Как строили Windows–приложения в самом начале — КиберПедия 

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

Архитектура электронного правительства: Единая архитектура – это методологический подход при создании системы управления государства, который строится...

Как строили Windows–приложения в самом начале

2020-03-31 135
Как строили Windows–приложения в самом начале 0.00 из 5.00 0 оценок
Заказать работу

 

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

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

· приложение поддерживало динамическую загрузку библиотек. В случае DOS все необходимое для работы приложения находилось в файле образа задачи, а в случае Windows большое количество функций, необходимых приложению, содержится в динамически загружаемых библиотеках (часто это компоненты операционной системы). В таких случаях говорят, что приложение импортирует (import) функции. В то же время приложение должно предоставлять часть своих функций для того, что бы их вызывала операционная система (как, скажем, оконная процедура). В этих случаях говорят, что приложение экспортирует (export) функции.

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

Обычная DOS–задача не могла делать таких вещей. Поэтому в Windows был принят новый формат выполняемого файла, а для нормального построения образа задачи пришлось изменить стандартный компоновщик (linker) так, чтобы он мог использовать информацию об экспортируемых и импортируемых функциях и собирать выполняемые файлы в новом формате. Соответственно, такой компоновщик нуждался в информации об экспортируемых и импортируемых функциях, а также о некоторых дополнительных параметрах Windows–приложения. Чтобы предоставить эту информацию компоновщику надо было написать специальный текстовой файл — файл описания модуля (def–файл).

В файле описания модуля перечислялись имена экспортируемых функций, имена импортируемых и библиотеки, содержащие функции, подлежащие импорту, задавался размер стека, давалось короткое описание приложения и пр. Это было не слишком удобно, так как при вызове какой–либо новой функции из Windows API (а их более 1000), необходимо было добавлять ее имя в файл описания модуля.

В тех случаях, когда приложение нуждалось в собственных ресурсах, необходимо было отдельно описать эти ресурсы в специальном текстовом файле описания ресурсов (rc–файле). Вместе с компиляторами до сих поставляется специальный компилятор ресурсов RC.EXE, который воспринимает файл описания ресурсов, компилирует его и создает файл ресурсов приложения (res–файл). Затем тот–же компилятор ресурсов может объединить уже построенный выполняемый файл с файлом ресурсов приложения.

Таким образом построение приложения состояло из следующих этапов:

· разработка исходных текстов —.c,.cpp,.h,.hpp,.rc и.def файлы;

· компиляция исходного текста программы — из.c и.cpp получаем.obj (иногда.lib);

· компиляция ресурсов приложения — из.rc получаем.res;

· компоновка приложения — из.obj и.lib получаем.exe;

· встраивание ресурсов приложения в выполняемый файл — из.exe и.res получаем рабочий.exe.

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

Следующий этап

 

Очевидно, что необходимость перечислять кучу имен функций в файле описания приложения никого не приводила в восторг. Поэтому на следующем этапе была включена поддержка Windows–приложений непосредственно в компиляторы[17]. Для этого было добавлено ключевое слово _export (иногда __export), которое применяется при описании экспортируемых функций непосредственно в тексте C–программы. Для таких функций компилятор включает в объектный файл специальную информацию, так что компоновщик может правильно собрать выполняемый файл. Так, например, было сделано в первом примере для оконной процедуры:

 

LRESULT WINAPI _export proc(...

 

Помимо экспортированных функций, в файле описания приложения было необходимо перечислять все импортированные функции. А этот список был самым большим (Windows экспортирует более 1000 функций, которые могут импортироваться приложениями). Для решения этой задачи был изменен формат библиотек объектных файлов, так что появилась возможность описать имена функций, экспортированных другими модулями. Так компоновщик задачи может определить, какие функции импортируются и из каких модулей.

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

Библиотеки со списками функций, которые можно импортировать из Windows (то есть экспортированных компонентами Windows) входят в состав всех компиляторов. Иногда может возникнуть необходимость использования нестандартного компонента (или собственной динамически загружаемой библиотеки), для которых соответствующей библиотеки с ссылками нет. В таком случае можно воспользоваться специальным приложением — implib.exe — которое входит в состав большинства компиляторов (если его нет в составе компилятора, то значит его возможности реализованы в каком–либо другом инструменте, как, например, wlib.exe в Watcom C/C++). Implib позволяет по имеющемуся файлу динамически загружаемой библиотеки (.dll) или файлу описания проекта модуля библиотеки (.def), содержащему список экспортированных функций, построить библиотечный файл (.lib), с ссылками на функции библиотеки.

Первоначально, стремясь максимально уменьшить время загрузки модулей в память, при экспортировании функций им присваивались уникальные номера (назначаются разработчиком, либо просто по порядку). Конкретная функция однозначно определяется именем экспортирующей библиотеки динамической загрузки и идентификатором функции. Для задания идентификаторов экспортируемых функций используется файл описания модуля. Однако использование номеров вместо имен является не слишком удобным для человека, поэтому в Windows используются оба метода — функции доступны как по их идентификаторам, так и по их именам. Для Windows API общепринятым методом считается использование идентификаторов — Microsoft следит за тем, что бы все документированные функции сохраняли свои идентификаторы. А в Win32 API предполагается использование имен; более того, Microsoft не гарантирует, что идентификаторы документированных функций не будут изменяться.

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

Современные компиляторы и системы поддержки проектов фактически остались в рамках рассмотренного порядка построения приложения. Небольшие изменения реализованы в разных компиляторах независимо друг от друга. Так, иногда включение файла ресурсов приложения в выполняемый модуль выполняется не компилятором ресурсов, а непосредственно компоновщиком; в других случаях специальные редакторы ресурсов позволяют обойтись без построения файла описания ресурсов (.rc), а создать непосредственно файл ресурсов приложения (.res). Особенно часто это делается в системах визуального программирования.

Если вы используете какую–либо систему поддержки проектов (Watcom IDE, Microsoft Developer Studio, Borland IDE, Symantec IDE и пр.) — а скорее всего это именно так — то вы должны только проследить за тем, что бы необходимые файлы были включены в проект. Система сама отследит, как и когда должен использоваться тот или иной исходный файл.

Обычно в проект включаются следующие файлы:

· исходные тексты на C и C++ — файлы с расширениями.c,.cpp,.c++;

· файл, содержащий ресурсы приложения, как правило только один, либо.rc либо.res;

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

· дополнительные объектные файлы.obj — отдельно включаются очень редко;

· файл описания модуля.def, только один, часто только при желании описать нестандартные параметры компоновки (см. ниже, в описании этого файла).

Файл описания модуля (.def)

 

Файл описания модуля (приложения) содержит обычный текст, который можно написать любым текстовым редактором. В качестве примера рассмотрим один из файлов описания модуля, использованный для построения приложения testapp.exe:

 

NAME            TESTAPP
DESCRIPTION 'TESTAPP - test application'
EXETYPE      WINDOWS
PROTMODE
STUB        'WINSTUB.EXE'
CODE       LOADONCALL MOVEABLE
DATA       MOVEABLE MULTIPLE
STACKSIZE 8192
HEAPSIZE     4096

 

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

Большинство компиляторов могут использовать собственные директивы, а также собственные расширения для параметров, задаваемых в директивах, не описанные в общих руководствах (как, к примеру, директива PROTMODE в приведенном примере). Кроме того список возможных директив в файлах описания модулей для Windows API и для Win32 API различается.

 

Windows API

 

Первая строка обычно задает имя модуля. Если вы строите приложение, то первой должна стоять директива NAME, а если динамически загружаемую библиотеку — LIBRARY. Указание обоих директив считается некорректным. Имя должно соответствовать имени файла, так для testapp.exe эта строка будет такой: NAME TESTAPP, а для mydll.dll — LIBRARY MYDLL.

LIBRARY dllname
NAME exename

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

DESCRIPTION ‘description text for module’

Следующая директива, если и присутствует, то всегда определяет, что данный модуль предназначен для работы в среде Windows (аналогичные файлы описания модулей могут применяться для OS/2).

EXETYPE WINDOWS

Еще две директивы предназначены для задания размеров стека и кучи. Задание размера стека менее 5K приводит к тому, что Windows сам увеличивает его до 5K. Задание размера кучи вообще абстрактно — главное, что бы не 0, так как Windows будет увеличивать кучу при необходимости (вплоть до наибольшего размера сегмента данных приложения — 64K). Размер кучи 0 говорит о том, что она просто не используется..

HEAPSIZE size
STACKSIZE size

Очень любопытная директива — STUB. О ней надо рассказать чуть подробнее. Ранее было отмечено, что для Windows–приложений был разработан собственный формат выполняемого модуля. Очевидно, что попытка запустить такой модуль на старой версии DOS, который не рассчитан на такую возможность, приведет к грубой ошибке, вплоть до зависания компьютера или порчи данных. Чтобы этого избежать, сделали так — Windows–приложение состоит из двух частей:

· Первая часть представляет собой небольшое приложение MS–DOS, называемую заглушкой (stub). Обычно это приложение просто пишет на экране фразу типа “This program must be run under Microsoft Windows.”. Заголовок этой заглушки чуть изменен, чтобы Windows мог отличить DOS–приложение от Windows–приложения, но это изменение находится в неиспользуемой MS–DOS’ом части заголовка.

STUB ‘stubexe.exe’

Здесь stubexe.exe — имя приложения–заглушки (возможно полное имя, вместе с путем)

· Вторая часть — собственно код и данные Windows–приложения с собственным заголовком.

Разрабатывая собственную заглушку можно делать интересные приложения, работающие в DOS и в Windows. Скажем, собрать в один выполняемый файл DOS–версию приложения и Windows–версию.

Еще три директивы используются для описания параметров сегментов кода и данных. Директива CODE задает характеристики сегментов кода, DATA — сегментов данных, а SEGMENTS позволяет описать характеристики для конкретного сегмента (в квадратные скобки [] заключены необязательные параметры, знак | разделяет возможные варианты; запись [FIXED|MOVEABLE] означает, что может быть указано либо FIXED, либо MOVEABLE, либо ничего). Более подробно о характеристиках сегментов приложения см. в описании диспетчера памяти Windows 3.x.

 

CODE [FIXED|MOVEABLE] [DISCARDABLE] [PRELOAD|LOADONCALL]

DATA [NONE|SINGLE|MULTIPLE] [FIXED|MOVEABLE]

SEGMENTS
      segname [CLASS ‘clsname’] [minalloc] [FIXED|MOVEABLE] [DISCARDABLE] [SHARED|NONSHARED] [PRELOAD|LOADONCALL]
     ...

 

Могут быть указаны следующие параметры: FIXED или MOVEABLE — сегмент фиксирован в памяти или перемещаемый; если сегмент перемещаемый, то он может быть теряемым (DISCARDABLE). Параметры PRELOAD и LOADONCALL указывают, когда сегмент должен быть загружен в оперативную память — при загрузке приложения (PRELOAD) или при непосредственном обращении к нему (LOADONCALL). Параметры NONE, SINGLE или MULTIPLE применяются только для сегментов данных. NONE или SINGLE применяется для динамически загружаемых библиотек; NONE — библиотека не имеет сегмента данных вообще, SINGLE — сегмент данных присутствует в памяти в единственном экземпляре (динамические библиотеки загружаются только один раз, других копий не существует, все приложения ссылаются на одну библиотеку с единым сегментом данных). MULTIPLE — сегмент данных загружается для каждой копии приложения, применяется только для приложений.

Директива SEGMENTS описывает характеристики конкретных сегментов; segname — имя сегмента, clsname — имя класса сегмента, minalloc — минимальный размер сегмента. SHARED или NONSHARED сообщает, является ли сегмент разделяемым разными копиями приложения, либо нет. После одной директивы SEGMENTS может размещаться описание нескольких сегментов, по одному на каждой строке.

Еще две часто используемых директивы — EXPORTS и IMPORTS. Они задают списки экспортированных и импортированных имен функций, описание каждой функции находится на своей строке. В обоих случаях надо учитывать следующую особенность — в Windows различают внешние и внутренние имена. Внутренние имена — это имена, использованные при разработке исходного текста программы, те которые содержаться в тексте C–программы (или в объектном файле). Внешние имена — имена используемые при экспорте/импорте, доступные другим модулям. Внешнее и внутреннее имя могут не совпадать, поэтому предусмотрена возможность задавать оба имени.

 

EXPORTS
      exportname [=internalname] [@id] [RESIDENTNAME] [NODATA] [argsize]
     ...

 

В разделе EXPORTS перечисляются имена функций, экспортируемых данным модулем. Параметр exportname задает внешнее имя функции, под которым она будет доступна другим модулям, параметр internalname используется, если внешнее и внутреннее имена различаются, @id задает идентификатор функции, argsize — если указан, сообщает сколько слов в стеке занимает список аргументов функции. Параметры RESIDENTNAME и NODATA используются крайне редко; RESIDENTNAME говорит о том, что имя функции должно размещаться в так называемом резидентном списке имен (который находиться в памяти постоянно после загрузки модуля), обычно имена размещаются в нерезидентном списке, который загружается при необходимости. NODATA говорит о том, что функция использует сегмент данных вызывающего модуля, а не экспортирующего (подробнее — при разговоре о диспетчере памяти).

 

IMPORTS
      [internalname=] modulename.id
      [internalname=] modulename.importname

 

Раздел IMPORTS задает соответствие внутренних имен импортируемых функций (internalname) функциям, экспортированным другими модулями. Возможно два метода связывания имен — по идентификатору (первый пример), modulename — имя экспортирующего модуля, id — идентификатор; либо по именам (второй пример), importname — внешнее имя функции, под которым она была экспортирована другим модулем.

Win32 API

 

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

Так как файл описания модуля используется для создания DLL, то первая директива — LIBRARY. Часто применяется также директива EXPORTS для задания идентификаторов экспортируемых функций (обе — см. в описании файла описания модуля для Windows API).

Для задания нестандартных параметров отдельных сегментов используется директива SECTIONS, заменяющая прежнюю директиву SEGMENTS. Синтаксис директивы SECTIONS несколько иной, хотя допускается замена слова SECTIONS на SEGMENTS:

 

SECTIONS
      secname [CLASS ‘classname’] [EXECUTE] [READ] [WRITE] [SHARED]

 

После указания имени секции (сегмента) следует необязательное указание имени класса и атрибутов этой секции — разрешение на выполнение (EXECUTE), на чтение (READ), на запись (WRITE) и объявление секции разделяемой (SHARED) между разными копиями модуля (загруженными в разных адресных пространствах разных приложений!).

 

Дополнительные разделы

 

В этом разделе будет рассказано о малоизвестном заголовочном файле — windowsx.h. В некоторых случаях разработчики его, конечно, используют, но редко больше чем на 5% от его возможностей. Этот заголовочный файл был разработан специально для облегчения контроля исходного текста программы. К сожалению, в большей части документации, сопровождающей компиляторы, этот файл вообще не описан, хотя существует уже очень давно. Пожалуй впервые он описан в документации, сопровождающей Microsoft Visual C++ 4.0 (Microsoft Developer Studio, раздел “SDKs|Win32 SDK|Guides|Programming Techniques|Handling Messages with portable macros”). Однако даже там описаны только принципы его применения, но не дано подробное описание всех его макросов. Как результат — при применении windowsx.h приходится постоянно обращаться к его исходному тексту.

 


Поделиться с друзьями:

Особенности сооружения опор в сложных условиях: Сооружение ВЛ в районах с суровыми климатическими и тяжелыми геологическими условиями...

Общие условия выбора системы дренажа: Система дренажа выбирается в зависимости от характера защищаемого...

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

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



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.043 с.