Hello, MiniProg 2Delphi , ОС и Железо , DOS и КонсольHello, MiniProg 2
Автор: Иванов Петр ака Brodia@a Прошло некоторое время, клавиатура остыла после тестирования и писательства, можно продолжать. Данная тема уже не раз поднималась на просторах Королевства, например, здесь или здесь. Огромное множество материала, на данную тематику, чьей то щедрой рукой, разбросано по интернету. Правда, все методы однотипные и сводятся к тому, что программа, при запуске, проверяет какой-нибудь признак, если он не обнаружен - то запускается, если же присутствует... В этом месте возможны самые различные реакции, от сообщений, с требованием ответа/нажатия кнопки, до коварнейших систем оповещения создателей (например, как у M$ XP :). Признаком может служить, либо проверка наличия определенного окна, либо отметка в конфигурационном файле/регистре, либо банальный файл, создаваемый при запуске приложения и удаляемый при выходе из него. Более сложные системы, профессионального уровня, обращаются за советом, "можно, или нельзя", к специализированным лицензионным серверам.
Сами функции располагаются в Appl.pas и выглядят так:
Теперь посмотрим, как можно будет показать найденную первую копию. Вариантов 'поискать и показать форму', в интернете, огромная масса. Тестовая процедура и сама функция выглядят так:
Компилируем, запускаем, проверяем - все работает, как требуется. Следует отметить, что в данном случае, тестировалось только возвращаемое ShowPrevInstance значение, сам эффект 'показа' незаметен. По этому, что бы ни уподобляться тому сапожнику, который без сапог, внесем в testMiniProg.dpr изменения, добавим в секцию uses модуль Appl и следующий код:
В модуль Appl.pas, поместим функцию FindAndShowPrevInstance, которая будет искать и активизировать предыдущую копию программы. Её тестирование проведем на функциональном уровне, так как технологическое тестирование, хоть и возможно, но реализовывать его будет обременительно. Впрочем, желающие могут попробовать, не забудьте только мне показать, очень интересно.
Компилируем, запускаем, пробуем запустить вторую копию - у меня всё, как и предполагалось. Ну что же, можем считать, что функциональные тесты данная функция прошла. Есть один момент, который нужно учитывать. Не очень удобно то, что 'DUnit', или какое-то другое, милое вашему сердцу заветное слово, приходится писать два раза. Мне, к сожалению, так и не удалось приравнять Application.Title ни константе, ни переменной. Все время возникала ошибка dcc32.exe, по-видимому, из-за того, что данное значение используется самим Delphi. Возможно изменение, в виде переноса проверки FindAndShowPrevInstance в секцию initialization модуля Appl.pas, StopPrevInstance в секцию finalization, а сам unit прописать в uses dpr вашей программы самым ПЕРВЫМ. В принципе, я обычно так и делаю, в данном же случае пример просто показательный, потому и несколько упрощенный. Не сомневаюсь, даже данный подход можно улучшить. Особенность передаваемого FindAndShowPrevInstance значения в том, что оно должно быть такое же, как и имя главной формы программы, в противном случае невозможно будет правильное выполнение StopPrevInstance. Конечно, проверка мьютекса будет выполнена, и 'лишнее' приложение буде закрыто, но активизации первой копии не произойдет. Если кого-то не устраивает такое положение дел, например, этот кто-то, всегда дает одно и тоже имя главному модулю своих программ, то всё можно поправить. Просто расширьте число предаваемых функции параметров - отдельно имя мьютекса, отдельно имя главного окна. Как видите, с помощью довольно бесхитростных средств нам удалось избежать атаки клонов собственных программ. Посмотрим, что можно сделать дальше. Способность сохранять в конфигурационном файле какие-нибудь значения, например, положение и размеры окна, так же была освещена в интернет очень широко. В RxLib, есть неплохой компонент, умеющий многое, я сам пользовался им когда-то. По этому не будем изобретать ничего нового, но просто воспользуемся уже известными приемами. Почему именно ini-файл, а не регистр, или не способ хранение свойств компонентов, так как это делает Delphi? Свои плюсы и минусы есть у всех подходов, но для наших целей вполне хватит возможностей ini-файла. Будем считать, что ini-файл располагается в директории вместе с программой и имеет такое же имя, но другое расширение, например "ini" :). Традиционно, свойства окна хранят в отдельной секции ini-файла, с уникальным, для данного приложения именем. Используем для этого имя формы. И так, тесты:
Сами же функции очень просты. В принципе, GetSectionName можно было бы расширить, включив возможность генерации имени секции для любого компонента, с учетом формы-владельца, но пока не будем этого делать:
Необходимо решить, какие именно значения свойств окна будут сохраняться и восстанавливаться. Вероятно состояние окна: свернуто, максимизировано и т.д. и позицию окна, т.е. положение левого верхнего угла и, либо положение правого нижнего угла, либо размеры окна. Необходимо еще предусмотреть, как средство защиты программы от пользователей - любителей запускать одно и то же приложение при разных значениях PPI, возможности, на выбор: 1) отказа от восстановления параметров окна и установка значений по умолчанию, Мне, больше по душе метод 'нумбер 2'. Что нужно сделать? вроде бы совсем не многое - всегда хранить размеры окна приведенными в соответствие с PPI времени создания, и при восстановлении проводить коррекцию, в соответствии с PPI времени выполнения. Положение левого верхнего угла формы изменять не следует, этого не делает Delphi, не будем делать и мы. В первой части статьи, говорилось, что величина масштабирования размеров окна зависит от отношения PPI's времен создания и выполнения, и такого понимания, тогда, было достаточно. Настало время все уточнить. На самом деле все обстоит несколько сложнее. Отношение PPI's используется для масштабирования высоты шрифта, после этого вычисляется высота образцового текста (у Delphi это строка '0' :). Ну а далее, для масштабирования, используется отношение старой и новой высот текста. Это отношение будет равно отношению PPI's в случае использования стандартных, для Windows, установок 'Крупный/Мелкий шрифт'. Размеры обычных экранных шрифтов строго фиксированы, по этому, использование нестандартных значений PPI' s может приводить к возникновению неприятных эффектов. В таких случаях, иногда, способен помочь шрифт TTF, например, как предлагается здесь. Следует отметить еще одну особенность масштабирования форм: непосредственно изменяются не сами размеры формы, а размеры клиентской части. Вооружившись этими знаниями можно придти к выводу, что придется вносить изменения в функцию RtmPPI и DsgnPPI, и вычислять их результат иначе, чем было сделано ранее. Идея проста, использовать для масштабирования высоту текста, времени создания формы и времени выполнения приложения. Судя по всему - это более корректный способ, однако, в названиях переменных и процедур сохранена аббревиатура PPI. Остается вопрос, где, во время исполнения, взять высоту текста времени создания, ведь при создании окна все размеры изменяются. В принципе, все интересующие нас числа хранятся в ресурсах программы и можно попробовать прочитать их оттуда. Но, все попытки обратиться к ресурсам формы в программе, использую стандартные и рекомендованные для этого средства, ни к чему не привели. Точнее, нужные ресурсы программы успешно читаются, но уже в измененном виде, так уж устроен метод TCustomForm.ReadState :(. По этому, попытаемся прочитать данные из ресурса, так же, как это делает Delphi, но в сильно упрощенном варианте. Если вы загляните в исходный код VCL и просмотрите всё, что хоть как-то касается загрузки ресурсов программы, то поймете, зачем эти упрощения. Сведений, в литературе и интернете, связанных с вопросами чтения ресурсов во время исполнения программы, без создания самих компонентов, очень мало. К моему сожалению, практически, я ничего не нашел, и если кто-то знает, где есть подобного рода информация - поделитесь ссылкой. Текст функции, которая читает ресурсы определенной формы, выглядит так:
Как я уже говорил, здесь представлен упрощенный вариант, который ищет определенный ресурс в программе, и сообщает в результате найден он или нет, а так же заполняет List набором строк найденных свойств и их значений. В список записываются не все свойства, а только те, которые определены в ресурсе и имеют тип, либо целого числа, либо строки, и принадлежат самой форме. При желании, можно организовать рекурсивный обход всех компонентов окна и чтение их свойств. Полный тест для данной функции не приводится, по той простой причине, что он довольно велик и явно выходит за рамки данной статьи. Может быть в другой статье :). Скажу лишь что, при построении такого рода функции, вряд ли стоит эмулировать полностью весь процесс загрузки ресурсов программы. В нашем случае, необходимо прочитать лишь некоторые свойства окна, что мы и сделаем. Конечно, можно поступить и так; создать пустую форму, у которой будет известна высота текста времени создания, но во время выполнения программы нам будет известно её масштабированное значение, что, собственно говоря, и нужно. Но, лично мне такой путь не нравиться, как по стилю решения проблемы, так и по тому, что при таком подходе возможна проблема с Constraints. Тестирующая функция довольно проста, хотя, конечно же, при тестировании полного варианта она выглядит иначе:
Изменим функцию RtmPPI таким образом, что бы она вычисляла, во время выполнения программы, высоту текста, для определенного нами окна. Соответственно DsgnPPI, изменится так, что вычисление её результата будет происходить с использованием ReadFormRes. Дополнительно, что бы избежать ошибок при определении RtmPPI, в ситуации, когда окно еще не создано, нам понадобится функция, которая по имени окна будет искать его в списке созданных форм и возвращать указатель на найденную форму, иначе nil.
В принципе, если бы создатели VCL, придерживались простого правила, присвоения nil указателю, который ссылается на еще не созданный или уже удаленный объект, многое было бы проще, и методологически вернее. И я не вижу ни каких логических объяснений, почему до сих пор это не сделано.
Эти функции проверяют наличие определенного окна . Если значение не равно nil, то считается, что форма уже создана, и у неё можно определить PPI's. Если форма еще не создана, то возвращается 0. В случае успешного выполнения функции, результатом будет значение высоты текста, отличное от 0. Так как сами функции несколько усложнились, то необходимо расширить их тестирование. Изменится так же, процедура TestDsgnVsRtmPPI, но функциональность её сохраниться, и даже несколько расшириться. Функция IsChangePPI удалена, из-за её несоответствия текущему моменту.
Вроде бы все подготовительные действия выполнены, можно попытаться сохранить/восстановить состояние формы. Текст тестовой функции TestSaveLoadFormState можно посмотреть в testAppl.pas. Логика проверки следующая, создается окно, с некоторой задержкой демонстрируется, запоминается состояние окна в локальной переменной и сохраняется в ini-файле. Устанавливаются другие значения состояния окна, перемещается, сворачивается в левый нижний угол, выжидается некоторое время. Восстанавливается состояние окна, сохраненное в ini-файле. Дополнительно, проводится проверка значений состояния окна, до и после сохранения/восстановления. Если же вас не убедят результаты тестов, то всегда можно будет заглянуть в файл ini и посмотреть всё своими глазами. Сами процедуры сохранения/восстановления, и все процедуры к которым они обращаются, приведены поименно ниже:
Полный текст процедур довольно велик по своим размерам, по этому здесь не приводится, но его можно посмотреть в Appl.pas, тестовые процедуры в testAppl.pas. Следует отметить, что при загрузке положения формы выполняется ScaleFormConstraints, которая корректирует значения Constraints окна, но другие элементы формы остаются без изменения. Желающие могут расширить её по своему усмотрению. Те, кто смотрел исходники MiniProg1, заметят, что в исходных файлах MiniProg2 проведены некоторые 'косметические' изменения. Продолжение следует ... Declaimer aka Отмазка. Я надеюсь, что люди, привыкшие читать академические труды, или слушать классические оперы, не станут осуждать автора, за его простую и незатейливую песнь кочевника. Что делал - о том и пел. Любые претензии и предложения принимаются в обсуждение и/или мылом. Все копирайты, если они известны, указаны. Иначе, автор не известен или копирайт утерян. Статья Hello, MiniProg 2 раздела ОС и Железо DOS и Консоль может быть полезна для разработчиков на Delphi и FreePascal. Комментарии и вопросыМатериалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта. :: Главная :: DOS и Консоль ::
|
|||||||||||||||||
©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007 |