Модульное программирование
Что такое модульное программирование
Модуль в программировании — это фрагмент кода, имеющий определенное функциональное значение и характеризующийся логической завершенностью.
Модульное программирование — это способ создания программы посредством объединения модулей в единую структуру.
Применение способа позволяет значительно повысить скорость разработки, обеспечить ее надежность, упростить тестирование. Модульное программирование удобно при групповой работе, так как каждый из участников процесса имеет возможность сконцентрироваться только на своем объеме работ, не отвлекаясь на деятельность коллег.
Осторожно! Если преподаватель обнаружит плагиат в работе, не избежать крупных проблем (вплоть до отчисления). Если нет возможности написать самому, закажите тут.
Важное преимущество подхода — возможность заменить или обновить один из компонентов без изменений всей остальной системы.
В чем состоит особенность, основные принципы
Основной принцип метода сформулирован канадским программистом Дэвидом Парнасом. Согласно его высказыванию, для создания одного модуля достаточно минимальных знаний о содержании других. Именно на основе этого принципа Парнас выдвинул концепцию сокрытия информации в программировании.
Сокрытие в программировании — это способ проектирования, предполагающий разграничение доступа разных частей продукта к внутренним компонентам друг друга.
Технологически процесс выглядит так:
В качестве структурных единиц могут выступать сервисы, классы, библиотеки функций, структуры данных и другие единицы, реализующие определенную функцию и способные предоставить интерфейс к ней.
Разновидности модулей
В информатике существуют дополнительные понятия модуля.
Модуль — это набор команд, который имеет свое обозначение и который можно вызвать по имени.
Модуль — это совокупность программных операторов, имеющая идентификатор и граничные компоненты.
Также принято выделять три разновидности:
Примерами крупных могут послужить набор пакетов в языках Java, Ada, а также набор модулей в программном языке Modula-2.
Проблемы модульного программирования
Несмотря на существенные достоинства метода, специалисты подмечают и ряд недостатков:
Если первые три пункта легко нивелируются мощью современной техники, то последний целиком и полностью зависит от программистов, оптимальности предложенных ими решений.
Структурное и модульное программирование.
Структурное программирование
Структурное программирование — парадигма программирования, в основе которой лежит представление программы в виде иерархической структуры блоков.
По своей сути оно воплощает принципы системного подхода в процессе создания и эксплуатации программного обеспечения ЭВМ.
Структурное программирование воплощает принципы системного подхода в процессе создания и эксплуатации программного обеспечения ЭВМ. В основу структурного программирования положены следующие достаточно простые положения:
Структурное программирование иногда называют еще «программированием без go to». Рекомендуется избегать употребления оператора перехода всюду, где это возможно, но чтобы это не приводило к слишком громоздким структурированным программам.
goto (перейти на) — оператор безусловного перехода (перехода к определённой точке программы, обозначенной номером строки либо меткой) в некоторых языках программирования. В некоторых языках оператор безусловного перехода может иметь другое имя (например, jmp в языках ассемблера).
Фундаментом структурного программирования является теорема о структурировании. Эта теорема устанавливает, что, как бы сложна ни была задача, схема соответствующей программы всегда может быть представлена с использованием ограниченного числа элементарных управляющих структур.
Базовыми элементарными структурами являются структуры: следование, ветвление и повторение (цикл), любой алгоритм может быть реализован в виде композиции этих трех конструкций.

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

Модуль должен обладать следующими свойствами:
Модули содержат определение доступных для обработки данных, операции обработки данных, схемы взаимосвязи с другими модулями.
Каждый модуль состоит из спецификации и тела. Спецификации определяют правила использования модуля, а тело – способ реализации процесса обработки.
Принципы модульного программирования программных продуктов во многом сходны с принципами нисходящего проектирования: сначала определяются состав и подчиненность функций, а затем — набор программных модулей, реализующих эти функции.
Почему никто не использует модульное программирование?
Есть понятия: «информационная шина [тип]», «[нужный тип] блок», «паттерн проектирования: [название]», «тип связи: [тип]», «узел [тип]» и т.д.
Все они формируются на этапе сопоставления данных и логики их обработки.
На деве, вышедшей из вод,
Плед, лента, брошь — все выдает
Старинный горделивый род.
Не всякой ленте довелось
Связать такую прядь волос,
Что чернотой своей была
Темней вороньего крыла;
Едва ль столь пламенную грудь
Плед прикрывал когда-нибудь;
Добрее сердца не найдешь,
Чем то, что заслоняла брошь.
Взор девы был и тих и мил
И кротость нежную струил,
Не в силах озерная гладь
Яснее берег отражать,
Чем отражал тот чистый взгляд
Что излучал тот жаркий ад
Который в мыслях воплощённый
Стараньем мудрости немой
Поведал нам про формы ночью
А мы тут все не в зуб ногой
Что нужно кодить всё шаблоном
Что много магии есть в нём
И что холодную порою
Мы выпускаем день за днём
Рюкзак набив клавиадромом
Релиз с крутейшею игрой.
Плед, лента, брошь — все выдает
Старинный горделивый род.
Не всякой ленте довелось
Та дева всех нас просветила
Не зря той ленте довелось
Связать такую прядь волос,
Что интеллектом и посылом
А эту строчку пропущу
Что нам всех благ она желает
Всегда разумна и горда
Но что-то здесь скребёт нам душу
Владычица Хаоса она.
Модули вместо микросервисов
Термин «модуль» (module) взят из статьи Modules vs. microservices. Так же для описания чего-то среднего между микросервисами и монолитами иногда используют термины «микролит» (microlith) или «моносервис» (monoservice). Но, не смотря на то, что термин «модуль» и так уже нагружен общеизвестным смыслом, на мой взгляд он подходит лучше других вариантов. Update: В комментарии lega использовал термин «встроенный микросервис» — он лучше описывает суть подхода, чем «модуль».
Монолит и микросервисы это очень разные подходы, поэтому в любой попытке взять лучшее от обоих критически важен баланс — что взять, а что нет. Иначе получится монстр вроде OSGi.
Я пишу микросервисы с 2009 года, но применять модули вместо микросервисов в реальных проектах пока не пробовал — всё описанное далее это моё предположение о том, каким должен быть вышеупомянутый баланс, и оно нуждается как в теоретической критике так и в проверке практикой.
Что такое модуль
Модуль — это что-то вроде микросервиса, только реализованного внутри единого приложения, состоящего из группы таких модулей и очень небольшой части, которая занимается инициализацией и запуском всех этих модулей.
Хотя формально такое приложение можно назвать монолитом, у этого подхода намного больше общего с микросервисами, поэтому и сравнивать его имеет смысл именно с микросервисным подходом. Как и микросервис, каждый модуль:
В отличие от микросервисов, модуль:
В отличие от обычных библиотек, модуль:
В большинстве случаев модули не нуждаются в каком-либо реестре сервисов — вместо регистрации себя и поиска других модулей они получают необходимые им интерфейсы других модулей при запуске, когда запускающееся приложение вызывает функцию инициализации каждого модуля (в порядке, определяемом зависимостями между модулями). В качестве побочного эффекта это позволит сразу обнаружить циклические зависимости между модулями, если они появятся (и, по возможности, изменить архитектуру так, чтобы от них избавиться).
Где нужны модули
Есть константная добавленная сложность (accidental complexity), одинаковая в каждом микросервисе (регистрация/обнаружение сервисов, подключение и переподключение к ним, авторизация между сервисами, (де)маршалинг и шифрование трафика, использование прерывателей зацикленных запросов, реализация трассировки запросов, etc.). Есть аналогичная константная добавленная операционная сложность (необходимость автоматизации тестирования и выката, реализация детального мониторинга, агрегация логов, использование служебных сервисов для регистрации и поиска сервисов, для хранения конфигурации сервисов, для аудита, etc.). С этим можно смириться, потому что реализовать всё это можно один раз, по мере роста количества микросервисов эта сложность не растёт, а преимущества микросервисов с лихвой компенсируют эти затраты.
Но есть сложность, зависящая от бизнес-логики конкретного приложения и увеличивающаяся по мере развития приложения, от которой хотелось бы избавиться хотя бы в тех приложениях, которым не требуется возможность масштабирования и высокая доступность (или хотя бы той части кода таких приложений, в которой нет явной необходимости взаимодействовать с внешними сервисами):
Правильный модульный подход позволяет сохранить многие преимущества микросервисов (при наличии необходимой поддержки на уровне языка и/или инструментов разработки), но помимо потери ненужных в данном приложении возможностей масштабирования и высокой доступности есть и другие:
Так же у модульного подхода появляются новые достоинства:
Резюме
В общем и целом подход выглядит достаточно соблазнительным — мы получаем возможность писать монолитное приложение в виде кучки по-настоящему хорошо изолированных частей (причём контролировать это будет по большей части язык и/или инструменты, а не внутренняя дисциплина), разрабатываемых в микросервисном стиле (в т.ч. разными командами в разных репо), которые «лёгким движением руки» могут превратиться в настоящие микросервисы если в этом возникнет реальная необходимость… А пока её нет — мы можем использовать обмен сообщениями между модулями внутри приложения как простую и очень быструю замену настоящего RPC, избегая сложностей асинхронности, eventual consistency и обработки сетевых ошибок.
Необходимая поддержка этого подхода в данный момент есть далеко не во всех языках, но в некоторых есть: автор статьи «Modules vs. microservices» писал о поддержке модульности в Java 9, в Go уже пару лет есть поддержка internal-пакетов, в Erlang судя по статье на эту же тему Dawn of the Microlith — Monoservices with Elixir всё хорошо, …. Я не уверен, насколько на скриптовых языках возможно обеспечить реальную изоляцию модулей, но попытки есть: micromono на NodeJS, в комментарии lega ссылка на подход для Python, …
Если у вас есть соображения по теме (а ещё лучше — опыт реального проекта на похожих принципах) или дополнительные ссылки на статьи/проекты по теме — пишите в комментариях, я постараюсь дополнять ими статью.
Модульное программирование в C++. Статические и динамические плагины
На тему модульности программ в С++, в Интернете, теоретических материалов много, а практических – мало. Поэтому, не найдя подходящего прототипа для собственного проекта, пришлось изобретать очередной «велосипед», из чего-то, ведь, исходить надо.
Сложность программирования, тестирования, отладки, поддержки и сопровождения программных продуктов заставляет искать пути борьбы с ней. Одним из традиционных способов является использование плагинов и сервисов с виртуальными интерфейсами. Однако доступные примеры предпочитают иметь дело с консолью, тогда как нам интересны окна и другие компоненты из арсенала GUI (графического интерфейса пользователя).
Часть первая. Результирующая
Введение
Предполагаем, что плагины реализуют оконные компоненты, а сервисы, как правило, безоконные либо вызывают служебные окна и диалоги. Их использование – вполне рабочая идея, но пути возможной реализации могут быть разными.
В нашем случае, удобно, разделить плагины на два вида: статические и динамические. К первым мы отнесем dll раннего связывания, т.е., бинарные модули, известные и доступные на этапе компиляции. А вторые это dll позднего связывания, загружаемые автоматически, из определенного каталога, во время работы основной программы.
Отличие плагинов, особенно динамических, от обычных dll, заключается, как правило, в использовании первыми виртуальных интерфейсов, о которых речь ниже. Единообразие подобных интерфейсов как раз и служит способом их подключения и использования во время работы программного кода.
В принципе, все приложение можно свести к плагинам, оставив только служебный код для подключения модулей. Естественно, что создаваемый либо отлаживаемый модуль должен быть представлен явно своим исходным кодом. При этом, в главном модуле, сам текст программы будет минимальным. Другими словами, для отладки и проверки работы своего модуля в общем приложении, можно использовать главный модуль, в виде простого исходного кода и свой собственный код. Все остальные динамические и статические плагины могут оставаться в своем бинарном виде.
Язык С++ очень удобен для решения задач подобного рода. Но, чтобы не усложнять демонстрационную программу различного рода фреймворками, мы ограничимся использованием только WinAPI.
Таким образом, в данной статье будет рассматриваться программа, ориентированная на классический графический интерфейс пользователя и выполняющая некоторые демонстрационные функции, основанные на программной логике.
Но сначала пара слов о модульности, как таковой.
Модульное структурирование кода
Под модульностью программ мы понимаем, прежде всего, возможность представления части исходного кода проекта, в виде бинарных независимых модулей. Более того, весь существенный код можно вынести во внешние бинарные модули, тогда как оставшийся код будет выполнять, по сути, только служебные функции.
Подобное разделение кода удобно для тестирования, отладки и быстрой компоновки отдельных частей приложения. Это может иметь смысл и для командной работы, когда каждый создает собственный модуль, а потом просто проверяет его работу путем копирования своей библиотеки в соответствующий каталог плагинов.
Однако может возникнуть ситуация, когда необходимо весь исходный код плагинов скомпилировать вместе с основным модулем. Технически это не должно вызывать проблем. Выбор должен быть на уровне параметра условной компиляции. Проще всего, просто сформировать два проекта, один для полной динамической сборки, а второй, для полной статической сборки проекта. Понятно, что допустимы и промежуточные варианты.
Результат работы программы без динамических плагинов
В нашем демо-проекте, мы использовали оба вида компиляции. Естественно, что результат работы у них один и тот же. Только, в динамическом проекте, можно видеть работу программы вообще без плагинов (рис. 1).
Рис. 1. Главное окно приложения при отсутствии плагинов.
В проекте нам доступны два динамических плагина и три статических. Статические плагины присутствуют всегда, либо внутренним образом (при полной статической сборке проекта), либо в виде трех dll (расположенных рядом с exe-модулем):
– Common.dll (библиотека общего назначения, для упрощенной работы с ini-файлами, создания исходных каталогов и т.п.);
– DllLoader.dll (загрузчик динамических плагинов, который оказался достаточно сложным для включения его в библиотеку общего назначения);
– App.dll (основной код, создающий главное окно, организующий работу цикла сообщений и т.п.).
Работа этих статических плагинов (за исключением создания прототипа главного окна) на рисунке не видна, поскольку они предназначены, в основном, для обслуживания динамических плагинов.
Сами же динамические плагины представлены двумя файлами (в папке «Plugins»):
– NewWin.dll (создание множества различных дочерних MDI-окон, одного класса)
и
– About.dll (единственное MDI-окно, эмулирующее диалоговое окно «О программе»).
Заметим, что эти плагины имеют собственные внешние ресурсы в папке «Plugins\Res».
Результат работы программы с динамическими плагинами
Если добавить плагин «NewWin.dll» в папку «Plugins», то увидим следующие изменения (рис. 2).
Рис. 2. Главное окно приложения при наличии плагина «NewWin.dll».
Этот плагин создает несколько различных окон с цитатами Владимира Маяковского. При этом, первое окно центрируется, а последующие располагаются по диагонали, со смещением (рис. 3).
Рис. 3. Множество окон плагина «NewWin.dll».
Если изменить размеры текущего окна, то следующее окно тоже изменится соответственно, только на отступы это не повлияет.
Хотя это и не принципиально, но мы ограничили количество одновременно открытых окон, при превышении которых будет выдано предупреждение (рис. 4).
Рис. 4. Максимальное количество окон плагина «NewWin.dll».
Добавим теперь плагин «About.dll». Результат его работы виден на рис. 5.
Рис. 5. Единственное окно «О программе» плагина «About.dll».
Поскольку это обычное окно, а не диалог, то оно может менять фокус, размеры и местоположение. При этом размеры окна сохраняются, но, при открытии, оно всегда центрируется.
Благодаря наличию внешних ресурсов у плагинов, все эти окна полностью настраиваются. Можно менять практически все, вплоть до пиктограмм, пунктов меню, «горячих» и Alt-клавиш для них. Не говоря уже о файлах изображений и тексте в окнах.
Заметим, что для Alt-клавиш мы использовали цифры, вместо символов. Это связано с раскладкой клавиатуры. Если, допустим, раскладка у нас латинская, а буквы русские либо наоборот, то Alt-клавиши действовать не будут. Цифры, в этом смысле, от раскладки не зависят. Однако латинские символы в файловых настройках указывать можно. О самих конфигурационных файлах речь ниже.
Принцип работы программы «AppPlugins»
Здесь можно отметить, что:
1. Приложение и плагины считывают исходные данные (размеры и наименование окон, пути меню, горячие и Alt-клавиши, текстовку и т.п.) и по ним строят окна и модифицируется меню. Если данных нет, то используются параметры по умолчанию. В качестве встроенного шрифта выбран «Arbat-Bold». Если его нет в вашей системе, то используется другой.
2. При выходе из программы, текущие данные сохраняются в упрощенных ini-файлах.
Отметим, что мы практически не пользуемся rc-файлами ресурсов вообще, поскольку все ресурсы у нас динамические. В том числе и пиктограммы окон и фоновый рисунок дочернего окна. Кстати, окна можно закрывать дополнительной горячей клавишей «Esc» (помимо стандартной комбинации «Ctrl+F4»). Однако эти ресурсы должны находиться в соответствующих каталогах, относительно исполняемого exe-файла. Исключение составляет иконка для exe-файла. Поскольку, просто непонятно, как ее прикрепить к результирующему файлу, во время компиляции проекта, без использования файла ресурсов. Все остальное можно загрузить в рантайме.
Проблемы модульности
Разделять код на логически независимые модули достаточно сложно. В первую очередь, это вызвано сильной связью между используемыми программными компонентами, что хорошо видно даже для простейшего случая.
Действительно, на самом нижнем уровне, разные компоненты приложения используют общие ресурсы, которые нужно разделять. Но, собственно, сильную связь между программами определяют взаимные вызовы прикладных функций между модулями программы.
При этом, целью модульности является не столько разрыв связей между программными компонентами, сколько их унификация и стандартизация. Обычно это достигается с помощью протоколов и интерфейсов.
Общие ресурсы
В общие ресурсы входят:
1. Общие переменные (внутренние, создаваемые в памяти, и внешние, загружаемые из файлов инициализации).
3. Общий цикл сообщений, в главном программном модуле, который обрабатывает, также, события от других модулей.
4. Общие функции и интерфейсы.
Общие переменные можно представить в виде структур, которые можно передавать по ссылке либо через указатель из одного модуля в другой. В качестве глобальных переменных можно оставить только (статические) константы.
Для целей модульности, общее меню уже нелогично делать монолитным, определяемом на уровне компиляции, в файле ресурсов. Более предпочтителен вариант, когда сам модуль добавляет нужные пункты меню, их обработчики и горячие клавиши.
Для третьего и четверного пунктов надо, первоначально, зарегистрировать плагины, т.е., получить указатели на классы (интерфейсы) внешних либо внутренних модулей и каждому из них поставить в соответствие идентификатор команды, такой же, как и для пункта меню, выполняющий этот модуль. А указатели на общие интерфейсы можно просто передавать в полях соответствующей структуры.
В общем цикле сообщений можно выделить диапазон команд (событий) которые по их номеру вызывают соответствующие регистраторы окон плагинов и их обработчики. Т.е., сделать так, чтобы эта часть работы программы не зависела от конкретного плагина, только от номера его интерфейса и стандартных функций в нем.
Часть вторая. Техническая
Общие цели
Задача, при создании нового проекта, была простой. Нужно было сразу организовать проект так, чтобы весь существенный код был вынесен во внешние бинарные модули. При этом, dll позднего связывания (динамические плагины) должны быть независимыми от приложения. Т.е., если они есть, то программа их «подхватывает» и использует, а если нет, то это никак не должно отражаться на работе основного приложения.
Кроме того, требуется, чтобы, заранее известные и необходимые модули (создание главного окна, загрузчик динамических плагинов, модуль общих функций и т.п.) организовать в виде dll раннего связывания (статические плагины). Они, конечно, являются обязательными (при полной динамической сборке проекта), но, способные заменяться на аналогичные, при наличии таковых.
При этом, в случае создания либо отладки отдельного модуля, можно весь проект подвергнуть полной статической сборке, что не должно вызывать лишних трудностей, либо ограничиться только частичной статической сборкой, для искомого и главного модуля, а для остальных – динамической сборкой.
Такой подход позволяет добиться распараллеливания сложного проекта на отдельные составляющие (как динамические, так и статические), что само по себе также является нашей целью.
Естественно, в начальной статье, речь можно вести только о самых простых случаях. А дальнейший процесс развития проекта, отражать уже в других статьях.
В подобной трактовке нет ничего особо нового, все уже давно известно и с успехом применяется на практике. Достаточно посмотреть многие открытые проекты, на том же Гитхабе, например. Однако найти подходящий прототип, на эту тему, для начинающих разработчиков, не удалось. Поэтому, цель статьи предоставить такой прототип. Который, в последствии, можно совершенствовать и развивать, в т.ч., благодаря конструктивной критике.
Мы сейчас не ведем речь о кроссплатформенности, оптимальном выборе компилятора и т.п. Для проверки общих идей это все не принципиально. Более того, чтобы не загромождать начальный проект избыточной сложностью и детализацией, предлагается ограничиться, в демонстрационной программе, следующими задачами:
1. Проект писать на C++ / WinAPI, как для 32-х, так и для 64-х разрядных систем Windows. При этом использовать бесплатную версию Visual Studio С++ 2017, Community Edition (требуется только регистрация). Хотя сама программа должна работать и в Visual Studio С++ 2010.
2. Приложение должно представлять из себя простое главное окно, способное поддерживать статические и динамические плагины. Главными для нас являются динамические плагины. Для демонстрации их работы вполне достаточно пары штук, каждый из которых создает и поддерживает собственный тип дочернего окна.
3. Поскольку, пока нет смысла делать плагины слишком сложными, то ограничимся следующими возможностями:
3.1. Первый плагин, который мы назовем «NewWin.dll» будет создавать множество дочерних MDI окон. Каждое из которых будет выводить некий логотип, рисовать какой-нибудь фон и писать определенный текст. Все параметры должны быть настраиваемыми, храниться в конфигурационном файле и использовать внешние ресурсы. Для определенности, мы выбрали, для логотипа – фото и подпись поэта Владимира Маяковского. Для текста – его стихотворные цитаты. Цветовая гамма для фона окна выбирается случайно, а изображение строится по некоторому произвольному алгоритму (рис. 2-3).
3.2. Второй плагин будет эмулировать стандартный диалог: «О программе». Это будет как бы упрощенный вариант первого плагина, позволяющий выводить только одно окно. А вместо программно генерируемого фона, там просто выводится внешний фоновый рисунок.
3.3. Каждый плагин добавляет свою запись в главное меню, «горячую» и Alt-клавишу для нее. Дополнительно (к команде «Ctrl+F4»), для MDI-окон должна действовать, как уже упоминалось выше, клавиша «Esc». Стандартная команда выхода из приложения: «Alt+F4» (ее программировать не надо). Но для нее будет доступен эквивалент в виде выбранной Alt-клавиши (рис. 1).
4. Для фиксированных dll раннего связывания (статических плагинов) будет реализован только минимально необходимый обслуживающий код.
Решение поставленной задачи методом «от обратного»
Посмотрим, каким может быть проект для главного модуля, в виде exe-файла, при полной динамической сборке (рис. 6). При этом два динамических плагина и три статических компилируются, в виде dll, независимо. Не нужно забывать об использовании необходимых lib-файлов (указанных в проектах). Последовательность компиляции, при полной динамической сборке, произвольная, но модуль «App.dll» компилируется предпоследним. Последним создается соответствующий исполнимый exe-файл. Для полной статической сборки создается только exe-модуль.
Рис. 6. Проект для главного модуля, при полной динамической сборке.
Отметим, что в папка «_BaseCode» является общей для всех проектов, независимо от способа их компиляции.
Как видим, здесь минимум кода (в файлах *.cpp). А «StdAfx.cpp» вообще пустой. Главными здесь являются файлы App.h и App.lib, которые связывают, на этапе компиляции, файлы Main.exe и App.dll.
Сам «Main.cpp» делает достаточно понятные вещи:
1. Получает указатель на интерфейс модуля приложения (либо из App.dll, либо из класса App.h / App.cpp).
2. Создает структуру общих параметров, записывает туда инстанс главного модуля и передает ее модулю приложения для дальнейшей инициализации, в т.ч., внешними параметрами, и использования. В принципе, инстанс можно вычислить и автономно, в самой dll, так что процедура эта скорее символическая.
3. Регистрация и создание главного окна приложения.
4. Запуск цикла сообщения главного окна.
Думаю, что здесь все просто и понятно. Вопрос только, что из себя представляет интерфейс модуля приложения?
Интерфейс модуля приложения
Откроем наш проект «App» (рис. 7).
Рис. 7. Проект «App», при полной динамической сборке модуля приложения.
Здесь вообще все просто. Код «Main.cpp» абсолютно стандартный для dll. Соответственно, главная содержательная часть находится в файлах «App.h» и «App.cpp». При этом данный модуль использует интерфейсы других dll: «Common.dll» и «DllLoader.dll», на что указывают файлы: «Common.h» / «Common.lib» и «DllLoader.h» / «DllLoader.lib». При этом все бинарные dll-модули компилируются независимо (как упоминалось выше, их у нас пять)
Посмотрим файл «App.h» из этого проекта. Там нас интересует класс «CAppInreface» (рис. 8-9).
Рис. 8. Заголовки и определения в файле «App.h».
Рис. 9. Класс «CAppInreface» в файле «App.h».
Это типичное оформление для виртуального класса, который мы перегружаем в классе «CApp» (рис. 10).
Рис.10. Перегруженный класс «CApp» в файле «App.h» для модуля приложения.
Здесь же можно найти и прототип экспортируемой функции «GetAppInterface()» (рис. 11).
Рис. 11. Прототип экспортируемой функции «GetAppInterface()».
А ее реализация находится в файле «App.cpp» (рис. 12):
Рис. 12. Реализация экспортируемой функции «GetAppInterface()».
Как видим, в принципе, все относительно просто. Сложны только нюансы реализации. Например, в нашем перегруженном классе «CApp» добавлены собственные функции и множество различных данных. Со всем этим надо работать, что требует много кода.
Однако, концептуально, виртуальные интерфейсы у нас одни и те же, как для статических плагинов, так и для динамических.
В любом случае, чтобы разобраться с проектом, надо с ним поработать. Как говориться: «Лучше один раз потрогать, чем сто раз увидеть» :).
Нюансы реализации
Их достаточно много, даже для этого небольшого проекта. Я бы обратил внимание на:
1. Логирование. Механизм здесь этот есть, но «замороженный». Я использовал его, когда надо было отследить маршруты оконных сообщений в циклах сообщений плагинов и главного окна.
2. Работа с ini-файлами. Оказалось, что нет особой необходимости работать с полноценными ini-файлами. Для этого надо много кода, который, для наших целей, вполне можно упростить. На рис. 13, показан пример файла «About.ini», генерируемого по умолчанию.
Рис. 13. Файл «About.ini», генерируемый по умолчанию.
Структура его очень простая, но всегда фиксированная, для использования в конкретном модуле:
– Строка комментария.
– Одна или несколько строк данных.
Строка комментария всегда начинается с символа ‘;‘. А строка / строки данных это все, что лежит после текущего комментария и до следующего комментария, либо конца файла.
Это позволило оформить достаточно просто громоздкие данные для плагина «NewWin.dll» (в файле «NewWin.ini» (рис. 14).
Рис. 14. Часть файла «NewWin.ini», генерируемого по умолчанию.
Если изменить здесь данные, без нарушения их структуры, то программа соответствующим образом отреагирует.
Заметим, что для отделения команды хоткея, используется символ табулятора ‘\t’. Кроме того, вместо процентов мы используем промилле (тысячную долю единицы), а вместо абсолютных размеров относительные. При желании это все можно поменять.
Кстати, все параметры у нас строковые, но поскольку их структура для каждого модуля нам известна, что числа из строк и обратно получаются соответствующими преобразованиями. При этом сам код чтения / записи наших ini-файлов, достаточно простой.
3. У нас используется достаточно много констант, для данных по умолчанию, определяемых в начале *.cpp файлов. Например, верхняя часть «App.cpp» показана на рис. 15-16.
Рис. 15. Константы и глобальное перечисление, для файла «App.cpp».
Рис. 16. Массивы данных, по умолчанию, для файла «App.cpp».
Это далеко не все имеющиеся нюансы реализации, но обо всем упоминать довольно долго. Лучше, не полениться и изучить прилагаемый код.
Статическая сборка
Поскольку весь существенный код у нас находится в каталоге «_BaseCode», то для полной статической сборки достаточно его подключить и убрать флаг IS_DLL (рис. 17).
Рис.17. Проект для главного модуля, при полной статической сборке.
Никакого другого изменения кода в наших проектах мы не делаем. Но не надо забывать, что проекты для статической и динамической сборки у нас хотя и разные, однако у них общий файл «StdAfx.h». Поэтому всегда нужно проверять, какое значение параметра IS_DLL выставлено там.
Все, компилируем и получаем ехе-шники, для х86 и х64, независящие от наших dll. Результаты всей нашей работы можно посмотреть, например, в TotalCommander’e (рис. 18). Имена для exe-файлов можно указывать любые.
Рис.18. Каталоги проекта для динамической и статической сборки.
Где скачать проект
Бинарные файлы, полученные в результате компиляции (для 32-х и 64-х разрядных систем) можно получить здесь:
emery-emerald.narod.ru/Articles/AppPlugins/AppPlugins_bin.zip
Компиляция под разными версиями Visual Studio C++
Данный проект скомпилирован под Visual Studio С++ 2017, Community Edition и SDK 10.0 как для x86, так и x64. Но он может быть получен и на меньших версиях и SDK 8.1, если в файлах, типа, *.vcxproj, сделать соответствующую замену для строк «ToolsVersion=«15.0»» и «v141»:
– В версии VS C++ 2010: «ToolsVersion=«4.0»» и «v100».
– В версии VS C++ 2013: «ToolsVersion=«12.0»» и «v120».
– В версии VS C++ 2015: «ToolsVersion=«14.0»» и «v140».


