Объект как динамический модуль

fashion 1031469 1920 Советы на день
Содержание
  1. Пошаговое руководство. Создание и использование динамических объектов (C# и Visual Basic)
  2. Предварительные требования
  3. Создание пользовательского динамического объекта
  4. Создание пользовательского динамического класса
  5. Создание примера текстового файла
  6. Создание примера приложения, в котором применяется пользовательский динамический объект
  7. Вызов библиотеки динамического языка
  8. Создание пользовательского динамического класса
  9. Понятие модуля. Принципы модульного программирования. Понятие объекта как динамического модуля.
  10. Модульное программирование в C++. Статические и динамические плагины
  11. Часть первая. Результирующая
  12. Введение
  13. Модульное структурирование кода
  14. Результат работы программы без динамических плагинов
  15. Результат работы программы с динамическими плагинами
  16. Принцип работы программы «AppPlugins»
  17. Проблемы модульности
  18. Общие ресурсы
  19. Часть вторая. Техническая
  20. Общие цели
  21. Решение поставленной задачи методом «от обратного»
  22. Интерфейс модуля приложения
  23. Нюансы реализации
  24. Статическая сборка
  25. Где скачать проект
  26. Компиляция под разными версиями Visual Studio C++

Пошаговое руководство. Создание и использование динамических объектов (C# и Visual Basic)

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

Динамические объекты обеспечивают удобный доступ к динамическим языкам, таким как IronPython и IronRuby. С помощью динамического объекта можно ссылаться на динамический скрипт, интерпретируемый во время выполнения.

Вы можете создавать настраиваемые динамические объекты, используя классы из пространства имен System.Dynamic. Например, можно создать объект ExpandoObject и задать члены этого объекта во время выполнения. Также можно создать собственный тип, наследующий класс DynamicObject. Затем для обеспечения динамических функциональных возможностей во время выполнения можно переопределить члены класса DynamicObject.

Эта статья содержит два независимых пошаговых руководства.

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

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

Предварительные требования

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

Создание пользовательского динамического объекта

Создание пользовательского динамического класса

Запустите Visual Studio.

Выберите Создать новый проект.

В диалоговом окне Создание нового проекта выберите C# или Visual Basic, выберите Консольное приложение, а затем нажмите кнопку Далее.

В диалоговом окне Настройка нового проекта введите значение DynamicSample для параметра Имя проекта и нажмите кнопку Далее.

В диалоговом окне Дополнительные сведения выберите значение .NET 5.0 (текущая) для параметра Целевая платформа, а затем нажмите кнопку Создать.

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

Будет добавлен новый файл, содержащий класс ReadOnlyFile.

В верхней части файла ReadOnlyFile.cs или ReadOnlyFile.vb добавьте следующий код для импорта пространств имен System.IO и System.Dynamic.

Пользовательский динамический объект использует перечисление для определения условия поиска. Перед оператором класса добавьте следующее определение перечисления.

Сохраните и закройте файл.

Создание примера текстового файла

В обозревателе решений щелкните проект DynamicSample правой кнопкой мыши и выберите Добавить > Новый элемент. На панели Установленные шаблоны выберите Общие, а затем шаблон Текстовый файл. В поле Имя оставьте имя по умолчанию TextFile1.txt и нажмите кнопку Добавить. В проект добавится новый текстовый файл.

Скопируйте в файл TextFile1.txt следующий текст.

Сохраните и закройте файл.

Создание примера приложения, в котором применяется пользовательский динамический объект

В обозревателе решений дважды щелкните файл Program.vb, если используется Visual Basic, или файл Program.cs, если используется Visual C#.

Сохраните файл и нажмите клавиши CTRL + F5 для сборки и запуска приложения.

Вызов библиотеки динамического языка

В следующем пошаговом руководстве создается проект, который осуществляет доступ к библиотеке, написанной на динамическом языке IronPython.

Создание пользовательского динамического класса

В Visual Studio выберите Файл > Создать > Проект.

В диалоговом окне Создание нового проекта выберите C# или Visual Basic, выберите Консольное приложение, а затем нажмите кнопку Далее.

В диалоговом окне Настройка нового проекта введите значение DynamicIronPythonSample для параметра Имя проекта и нажмите кнопку Далее.

В диалоговом окне Дополнительные сведения выберите значение .NET 5.0 (текущая) для параметра Целевая платформа, а затем нажмите кнопку Создать.

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

Установите пакет NuGet IronPython.

Если вы используете Visual Basic, отредактируйте файл Program.vb. Если вы используете Visual C#, отредактируйте файл Program.cs.

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

Сохраните файл и нажмите клавиши CTRL + F5 для сборки и запуска приложения.

Источник

Понятие модуля. Принципы модульного программирования. Понятие объекта как динамического модуля.

Понятие модуля. Принципы модульного программирования. Понятие объекта как динамического модуля.

Понятие модуля

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

Модуль – понятие многозначное. Возможны следующие определения: 1) библиотека процедур, которая может подключать другие модули и использоваться другими модулями; 2) файл на диске; 3) единица компиляции; 4) единица разработки.

Основные требования расширяемого программирования относительно модулей: созданный и отлаженный модуль уже не изменяется.

Модульное программирования

Модульное программирование – это организация программы как совокупности небольших независимых блоков (модулей), структура и поведение которых подчиняются определенным правилам.

Основные концепции модульного программирования:

-каждый модуль имеет единственную точку входа и выхода;

-размер модуля по возможности должен быть минимизирован;

-вся система построена из модулей;

-каждый модуль не зависит от того, как реализованы другие модули.

Основной принцип модульного программирования – «разделяй и властвуй».

Использование модульного программированияпозволяет упростить тестирование программы и обнаружение ошибок. Аппаратно-зависимые подзадачи могут быть строго отделены от других подзадач, что улучшает мобильность создаваемых программ.

Термин «модуль» в программировании начал использоваться в связи с внедрением модульных принципов при создании программ. В 70-х годах под модулем понимали какую-либо процедуру или функцию, написанную в соответствии с определенными правилами. Например: «Модуль должен быть простым, замкнутым (независимым), обозримым (от 50 до 100 строк), реализующим только одну функцию задачи, имеющим одну входную и одну выходную точку».

Первым основные свойства программного модуля более-менее четко сформулировал Парнас: «Для написания одного модуля должно быть достаточно минимальных знаний о тексте другого». Таким образом, Парнас первым выдвинул концепцию скрытия информации (information hiding) в программировании. Однако существовавшие в языках 70-х годов только такие синтаксические конструкции, как процедура и функция, не могли обеспечить надежного скрытия информации, поскольку подвержены влиянию глобальных переменных, поведение которых в сложных программах бывает трудно предсказуемым. Решить эту проблему можно было только разработав новую синтаксическую конструкцию, которая не подвержена влиянию глобальных переменных. Такая конструкция была создана и названа модулем.

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

Впервые специализированная синтаксическая конструкция модуля была предложена Н. Виртом в 1975 г. и включена в его новый язык Modula. Насколько сильно изменяются свойства языка при введении механизма модулей, свидетельствует следующее замечание Н.Вирта, сделанное им по поводу более позднего языка Модула-2: «Модули – самая важная черта, отличающая язык Модула-2 от его предшественника Паскаля».

По своей организации и характеру использования в программе модули Паскаля близки к модулям-пакетам (PACKAGE) языка программирования Ада. В них так же, как и в пакетах Ады, явным образом выделяется некоторая «видимая» интерфейсная часть, в которой сконцентрированы описания глобальных типов, констант, переменных, а также приводятся заголовки процедур и функций. Появление объектов в интерфейсной части делает их доступными для других модулей и основной программы. Тела процедур и функций располагаются в исполняемой части модуля, которая может быть скрыта от пользователя.

Общая структура программного модуля Delphi:

Заголовок модуля unit ;

Интерфейсная часть interface

Заголовки процедур procedure ( );

Заголовки функций function ( ): ;

Часть реализации implementation

Реализация процедур procedure ; begin. end;

Реализация функций function ; begin. end;

Код инициализации initialization

Код завершения finalization

После слова unit записывается имя модуля. Оно должно совпадать с именем файла, в котором находится исходный текст модуля. Заголовок модуля формируется автоматически при сохранении файла на диске, поэтому его не следует изменять вручную. Чтобы дать модулю другой заголовок, просто сохраните его на диске под другим именем.

Interface – глобальные данные, процедуры и функции, доступные для использования в основной программе и других модулях.

Implementation – программный код глобальных процедур и функций и описываются локальные данные, процедуры и функции, недоступные основной программе и другим модулям.

Блок initialization является необязательным. Он состоит из операторов и выполняется автоматически непосредственно перед запуском основной программы. Блоки инициализации подключенных к программе модулей выполняются в том порядке, в котором они упоминаются в секции uses.

Блок finalization тоже является необязательным. Он состоит из операторов и выполняется автоматически непосредственно после завершения основной программы. Блоки завершения подключенных к программе модулей выполняются в порядке, обратном порядку подключения модулей в секции uses.

Все стандартные модули Delphi можно разбить на две группы: системные модули и модули визуальных компонентов.

К системным модулям относятся System, SysUtils, ShareMem, Math. В них содержатся наиболее часто используемые в программах типы данных, константы, переменные, процедуры и функции. Модуль System — это сердце среды Delphi; содержащиеся в нем подпрограммы обеспечивают работу всех остальных модулей системы. Модуль System подсоединяется автоматически к каждой программе и его не надо указывать в операторе uses.

Модули визуальных компонентов (VCL — Visual Component Library) используются для визуальной разработки полнофункциональных GUI-приложений. Эти модули – высокоуровневая объектно-ориентированная библиотека со всевозможными элементами пользовательского интерфейса: кнопками, надписями, меню, панелями и т.д. + средства доступа к базам данных. Данные модули подключаются автоматически при помещении компонентов на форму.

Предположим, что на ассемблере написаны и скомпилированы функции Min и Max, их объектный код находится в файле MINMAX.OBJ. Подключение функций Min и Max к программе на языке Delphi будет выглядеть так:

function Min(X, Y: Integer): Integer; external;

function Max(X, Y: Integer): Integer; external;

В модулях внешние подпрограммы подключаются в разделе implementation.

Понятие объекта как динамического модуля

Понятие класса. Понятие метода. Представление метода в виде обычной процедуры. Понятие конструктора и деструктора.

Понятие класса

Объект – экземпляр класса.

Классы объектов определяются в секции type глобального блока. Описание класса начинается с ключевого слова class и заканчивается ключевым словом end. По форме похоже на обычные записи, но помимо полей данных классы могут содержать объявления пользовательских процедур и функций.

Items : array of string;

procedure PutItem(Index: Integer; constItem: string);

function GetEndOfFile: Boolean;

procedure SetActive(const _Active: Boolean);

Класс содержит поля и методы. Заголовки методов, следующие за списком полей, играют роль упреждающих (forward) описаний.

Класс обычно описывает сущность, моделируемую в программе.

Объявление ссылки на объект класса:

var Reader: TDelimitedReader;

Объекты в Delphi – динамические данные, т.е. распределяются в динамической памяти. Поэтому переменная Reader — это просто ссылка на экземпляр (объект в памяти), которого физически еще не существует.

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

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

TReadersList = class; // упреждающее объявление классаTReadersList

Readers: array ofTDelimitedReader;

Классы удобно помещать в модули: описание помещается в секцию interface, а код методов — в секцию implementation. Создавая модули классов, нужно придерживаться следующих правил:

– все классы, предназначенные для использования за пределами модуля, следует определять в секции interface;

– описание классов, предназначенных для употребления внутри модуля, следует располагать в секции implementation;

– если модуль B использует модуль A, то в модуле B можно определять классы, порожденные от классов модуля A.

Понятие метода

Метод – процедура или функция, определенная внутри класса,предназначенная для выполнения действий над объектами. Предварительное объявление методов выполняется при описании класса в секции interface модуля, а их программный код записывается в секции implementation. Однако в отличие от обычных процедур и функций заголовки методов должны иметь уточненные имена, т.е. содержать наименование класса.

procedure TDelimitedReader.SetActive(const _Active: Boolean);

Внутри методов обращения к полям и другим методам выполняются как к обычным переменным и подпрограммам без уточнения экземпляра объекта. Такое упрощение достигается путем использования в пределах метода псевдопеременной Self (стандартный идентификатор). Физически Self – дополнительный неявный параметр, передаваемый в метод при вызове. Он указывает экземпляр объекта, к которому данный метод применяется.

Понятие свойства

Свойство – виртуальное поле, физически не существующее (не занимают места в памяти). При чтении/записи отображается на поле, метод.

Свойства-массивы

Кроме обычных свойств в объектах существуют свойства-массивы (array properties).

Свойство–массив – это индексированное множество значений.

FItems: array of string;

function GetItem(Index: Integer): string;

property Items[Index: Integer]: string read GetItem;

function TDelimitedReader.GetItem(Index: Integer): string;

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

Основная выгода от применения свойства-массива – возможность выполнения итераций с помощью цикла for, например:

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

Свойства-массивы имеют два важных отличия от обычных массивов:

1) их индексы не ограничиваются диапазоном;

2) индексы могут иметь любой тип данных, а не только Integer:

Операции целиком со всем свойством-массивом запрещены; разрешены операции только с его элементами.

Свойство-массив можно сделать основным свойством объектов данного класса. Для этого в описание свойства добавляется слово default:

Property Items[Index: Integer]: string readGetItem; default;

Такое объявление свойства Items позволяет рассматривать сам объект класса TDelimitedReader как массив и опускать имя свойства-массива при обращении к нему из программы, например:

Следует помнить, что только свойства-массивы могут быть основными свойствами объектов; для обычных свойств это недопустимо.

Один и тот же метод может использоваться для получения (установки) значений нескольких свойств одного типа. В этом случае каждому свойству назначается целочисленный индекс, который передается в метод чтения (записи) первым параметром.

property FirstName : string index 0 read GetItem;

property LastName : string index 1 read GetItem;

property Phone : string index 2 read GetItem;

Обращения к свойствам FirstName, LastName и Phone заменяются компилятором на вызовы одного и того же метода GetItem, но с разными значениями параметра Index:

Writeln(Reader.FirstName); // Эквивалентно: Writeln(Reader.GetItem(0));

Writeln(Reader.LastName); // Эквивалентно: Writeln(Reader.GetItem(1));

Writeln(Reader.Phone); // Эквивалентно: Writeln(Reader.GetItem(2));

Индексаторы

Индексаторы очень похожи на свойства. Формально синтаксис определения индексатора таков:


[атрибуты] [модификаторы] тип this [список-формальных-параметров] < set get >

Или, если это индексатор в интерфейсе, таков:

[атрибуты] [модификаторы] тип интерфейс.this [список формальных параметров]

АТРИБУТЫ — дополнительная информация об индексаторе. Наиболее значимым для индексаторов является атрибут Name. Задание Name позволяет дать имя индексатору для того, чтобы его могли использовать другие языки, не поддерживающие индексаторы. По умолчанию все индексаторы вашего класса имеют Name, равный Item;

МОДИФИКАТОРЫ — модификаторы доступа и директивы. К индексатору применимы почти все стандартные директивы С#. Он может быть скрыт, перегружен, сделан виртуальным, но есть одно исключение, индексатор не может быть static;

СПИСОК ФОРМАЛЬНЫХ ПАРАМЕТРОВ — указывает параметры, посредством которых осуществляется индексация. Передается в get и set, которые используются в индексаторе так же, как в свойствах, get применяется для вычисления индексатора по заданному списку формальных параметров, a set – для изменения индексатора, set получает в качестве дополнительного параметра value того же типа, что и индексатор.

Следует отметить, что доступ к индексатору осуществляется посредством сигнатуры, в отличие от свойства, доступ к которому осуществляется посредством имени. Сигнатурой индексатора считаются число и тип формальных параметров. Тип самого индексатора и имена параметров в сигнатуру не входят. Естественно, в классе не может быть двух индексаторов с одинаковой сигнатурой. К тому же, индексатор не считается переменной и не может быть передан в качестве ref или out параметра.

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

Источник

Модульное программирование в C++. Статические и динамические плагины

e084f372308c6e921f8d49d82b7c9a75

На тему модульности программ в С++, в Интернете, теоретических материалов много, а практических – мало. Поэтому, не найдя подходящего прототипа для собственного проекта, пришлось изобретать очередной «велосипед», из чего-то, ведь, исходить надо.

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

Часть первая. Результирующая

Введение

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

В нашем случае, удобно, разделить плагины на два вида: статические и динамические. К первым мы отнесем dll раннего связывания, т.е., бинарные модули, известные и доступные на этапе компиляции. А вторые это dll позднего связывания, загружаемые автоматически, из определенного каталога, во время работы основной программы.

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

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

Язык С++ очень удобен для решения задач подобного рода. Но, чтобы не усложнять демонстрационную программу различного рода фреймворками, мы ограничимся использованием только WinAPI.

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

Но сначала пара слов о модульности, как таковой.

Модульное структурирование кода

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

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

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

Результат работы программы без динамических плагинов

В нашем демо-проекте, мы использовали оба вида компиляции. Естественно, что результат работы у них один и тот же. Только, в динамическом проекте, можно видеть работу программы вообще без плагинов (рис. 1).

image loader
Рис. 1. Главное окно приложения при отсутствии плагинов.

В проекте нам доступны два динамических плагина и три статических. Статические плагины присутствуют всегда, либо внутренним образом (при полной статической сборке проекта), либо в виде трех dll (расположенных рядом с exe-модулем):

– Common.dll (библиотека общего назначения, для упрощенной работы с ini-файлами, создания исходных каталогов и т.п.);
– DllLoader.dll (загрузчик динамических плагинов, который оказался достаточно сложным для включения его в библиотеку общего назначения);
– App.dll (основной код, создающий главное окно, организующий работу цикла сообщений и т.п.).

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

Сами же динамические плагины представлены двумя файлами (в папке «Plugins»):

– NewWin.dll (создание множества различных дочерних MDI-окон, одного класса)
и
– About.dll (единственное MDI-окно, эмулирующее диалоговое окно «О программе»).

Заметим, что эти плагины имеют собственные внешние ресурсы в папке «Plugins\Res».

Результат работы программы с динамическими плагинами

Если добавить плагин «NewWin.dll» в папку «Plugins», то увидим следующие изменения (рис. 2).

image loader
Рис. 2. Главное окно приложения при наличии плагина «NewWin.dll».

Этот плагин создает несколько различных окон с цитатами Владимира Маяковского. При этом, первое окно центрируется, а последующие располагаются по диагонали, со смещением (рис. 3).

image loader
Рис. 3. Множество окон плагина «NewWin.dll».

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

Хотя это и не принципиально, но мы ограничили количество одновременно открытых окон, при превышении которых будет выдано предупреждение (рис. 4).

image loader
Рис. 4. Максимальное количество окон плагина «NewWin.dll».

Добавим теперь плагин «About.dll». Результат его работы виден на рис. 5.

image loader
Рис. 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-модуль.

image loader
Рис. 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).

image loader
Рис. 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).

image loader
Рис. 8. Заголовки и определения в файле «App.h».

image loader
Рис. 9. Класс «CAppInreface» в файле «App.h».

Это типичное оформление для виртуального класса, который мы перегружаем в классе «CApp» (рис. 10).

image loader
Рис.10. Перегруженный класс «CApp» в файле «App.h» для модуля приложения.

Здесь же можно найти и прототип экспортируемой функции «GetAppInterface()» (рис. 11).

image loader
Рис. 11. Прототип экспортируемой функции «GetAppInterface()».

А ее реализация находится в файле «App.cpp» (рис. 12):

image loader
Рис. 12. Реализация экспортируемой функции «GetAppInterface()».

Как видим, в принципе, все относительно просто. Сложны только нюансы реализации. Например, в нашем перегруженном классе «CApp» добавлены собственные функции и множество различных данных. Со всем этим надо работать, что требует много кода.

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

В любом случае, чтобы разобраться с проектом, надо с ним поработать. Как говориться: «Лучше один раз потрогать, чем сто раз увидеть» :).

Нюансы реализации

Их достаточно много, даже для этого небольшого проекта. Я бы обратил внимание на:

1. Логирование. Механизм здесь этот есть, но «замороженный». Я использовал его, когда надо было отследить маршруты оконных сообщений в циклах сообщений плагинов и главного окна.

2. Работа с ini-файлами. Оказалось, что нет особой необходимости работать с полноценными ini-файлами. Для этого надо много кода, который, для наших целей, вполне можно упростить. На рис. 13, показан пример файла «About.ini», генерируемого по умолчанию.

image loader
Рис. 13. Файл «About.ini», генерируемый по умолчанию.

Структура его очень простая, но всегда фиксированная, для использования в конкретном модуле:

– Строка комментария.
– Одна или несколько строк данных.

Строка комментария всегда начинается с символа ‘;‘. А строка / строки данных это все, что лежит после текущего комментария и до следующего комментария, либо конца файла.

Это позволило оформить достаточно просто громоздкие данные для плагина «NewWin.dll» (в файле «NewWin.ini» (рис. 14).

image loader
Рис. 14. Часть файла «NewWin.ini», генерируемого по умолчанию.

Если изменить здесь данные, без нарушения их структуры, то программа соответствующим образом отреагирует.

Заметим, что для отделения команды хоткея, используется символ табулятора ‘\t’. Кроме того, вместо процентов мы используем промилле (тысячную долю единицы), а вместо абсолютных размеров относительные. При желании это все можно поменять.

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

3. У нас используется достаточно много констант, для данных по умолчанию, определяемых в начале *.cpp файлов. Например, верхняя часть «App.cpp» показана на рис. 15-16.

image loader
Рис. 15. Константы и глобальное перечисление, для файла «App.cpp».

image loader
Рис. 16. Массивы данных, по умолчанию, для файла «App.cpp».

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

Статическая сборка

Поскольку весь существенный код у нас находится в каталоге «_BaseCode», то для полной статической сборки достаточно его подключить и убрать флаг IS_DLL (рис. 17).

image loader
Рис.17. Проект для главного модуля, при полной статической сборке.

Никакого другого изменения кода в наших проектах мы не делаем. Но не надо забывать, что проекты для статической и динамической сборки у нас хотя и разные, однако у них общий файл «StdAfx.h». Поэтому всегда нужно проверять, какое значение параметра IS_DLL выставлено там.

Все, компилируем и получаем ехе-шники, для х86 и х64, независящие от наших dll. Результаты всей нашей работы можно посмотреть, например, в TotalCommander’e (рис. 18). Имена для exe-файлов можно указывать любые.

image loader
Рис.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».

Источник

Оцените статью
Добавить комментарий

Adblock
detector