Средство ClassWizard предоставляет широкий спектр услуг. Он позволяет не только добавлять к существующему классу новые методы и данные.
Создание нового класса. При помощи ClassWizard можно добавить новый класс, созданный на основе базовых классов. В качестве базового класса можно использовать классы, наследованные от класса CCmdTarget или класса CRecordset. Для наследования классов от других базовых классов использовать средства ClassWizard нельзя. Такие классы надо создавать вручную, непосредственно в текстовом редакторе.
Объекты, порожденные от класса CCmdTarget, могут обрабатывать сообщения Windows и команды, поступающие от меню, кнопок, акселераторов. Класс CCmdTarget и другие наследованные от него классы имеют таблицу сообщений (Message Map) - набор макрокоманд, позволяющий сопоставить сообщения Windows и команды метода класса.
Полученная заготовка класса полностью работоспособна. Ее можно дополнить по своему усмотрению новыми методами и данными. Эту работу можно выполнить вручную, но гораздо лучше и проще воспользоваться услугами ClassWizard. За счет использования ClassWizard процедура создания собственного класса значительно ускоряется и уменьшается вероятность совершить ошибку во время объявления методов.
Включение в класс новых методов. Очень удобно использовать ClassWizard для включения в состав класса новых методов. Можно добавлять к классу методы, служащие для обработки сообщений Windows и команд от объектов, а также методы, переопределяющие виртуальные методы базовых классов.
ClassWizard не только позволяет добавить в класс новые методы, но и удалить их. ClassWizard самостоятельно удалит объявление метода из класса.
Включение в класс новых элементов данных. ClassWizard позволяет включать в класс не только новые методы, но и элементы данных, связанные с полями диалоговых панелей, форм просмотра и форм для просмотра записей баз данных и полей наборов записей. ClassWizard использует специальные процедуры, чтобы привязать созданные им элементы данных к класса к полям диалоговых панелей. Эти процедуры носят названия "обмен данными диалоговой панели" и "проверка данных диалоговой панели" (Dialog Data Exchange and Dialog Data Validation - DDX/DDV). Чтобы привязать поля из наборов записей к переменным, используется процедура обмена данными с полями записей (Record Field Exchange - RFX).
Процедуры DDX/DDV и RFX значительно упрощают программисту работу с диалоговыми панелями. Они позволяют связать поля диалоговых панелей и переменные. Когда пользователь редактирует поля диалоговых панелей, процедуры DDV проверяют введенные значения и блокируют ввод запрещенных значений. Затем процедуры DDX автоматически копируют содержимое полей диалоговых панелей в привязанные к ним элементы данных класса. И наоборот, когда приложение изменяет элементы данных класса, привязанные к полям диалоговой панели, процедуры DDX могут сразу отобразить новые значения полей на экране компьютера.
Практически все приложения имеют пользовательский интерфейс, построенный на основе окон. Это может быть диалоговая панель, одно окно или несколько окон, связанных вместе. Основные свойства окон представлены классом CWnd, наследованным от класса CCmdTarget.
Программисты очень редко создают объекты класса CWnd. Класс CWnd сам является базовым классом для большого количества классов, представляющих разнообразные окна. Перечислим классы, наследованные от базового класса CWnd.
Обрамляющие окна (класс CFrameWnd). Класс CFrameWnd представляет окна, выступающие в роли обрамляющих окон, в том числе главные окна приложений. От этого класса также наследуются классы CMDIChildWnd и CMDIFrameWnd, используемые для отображения окон многооконного интерфейса MDI. Класс CMDIFrameWnd представляет главное окно приложения MDI, а класс CMDIChildWnd - его дочерние окна MDI. Класс CMiniFrameWnd применяется для отображения окон уменьшенного размера. Такие окна обычно используются для отображения в них панели управления.
Окна органов управления. Для работы с органами управления (кнопки, полосы прокрутки, редакторы текста и т.д.) в библиотеке MFC предусмотрены специальные классы, наследованные непосредственно от класса CWnd. Перечислим эти классы:
CAnimate - используется для отображения видеоинформации.
CBitmapButton - кнопка с рисунком.
CButton - кнопка.
CComboBox - список с окном редактирования.
CEdit - поле редактирования.
CHeaderCtrl - заголовок для таблицы.
CHotKeyCtrl - предназначен для ввода комбинации клавиш акселераторов.
CListBox - список.
CListCtrl - может использоваться для отображения списка пиктограмм.
CProgressCtrl - линейный индикатор.
CPropertySheet - блокнот. Может состоять из нескольких страниц.
CRichEditControl - окно редактирования, в котором можно редактировать форматированный текст.
CScrollBar - полоса просмотра.
CSliderCtrl - движок.
CSpinButtonCtrl - обычно используется для увеличения или уменьшения значения какого-либо параметра.
CStatic - статический орган управления.
Как уже говорилось, программы, созданные с использованием СОМ, предоставляет свои сервисы через один или несколько СОМ-объектов. Каждый такой объект является экземпляром некоторого класса и поддерживает определенное количество интерфейсов, обычно не менее двух. В состав каждого интерфейса входит один или более методов — функций, которые могут вызываться клиентом объекта. Например, один из гипотетических объектов, обсуждавшихся в предыдущей лекции, поддерживает как корректировщик орфографии, так и словарь синонимов, предоставляя доступ к своим сервисам через два разных интерфейса. Интерфейс корректировщика орфографии содержит LookUpWord, AddToDictionary и RemoveFromDictionary, тогда как интерфейс словаря синонимов состоит лишь из одного метода ReturnSynonym. Чтобы вызывать любой из этих методов, у клиента объекта должен быть указатель на интерфейс, содержащий соответствующий метод.
Каждый объект СОМ обязан поддерживать IUnknown — это не менее справедливо для объектов, доступ к которым осуществляется по сети. Но если бы каждый вызов клиентом метода lUnknown транслировался непосредственно в вызов ORPC объекта, результатом почти наверняка была бы неприемлемая производительность. В связи с этим DCOM обеспечивает оптимизацию этих важных методов.
В основе этой оптимизации лежат объекты OXID (OXID objects), каждый из которых поддерживает интерфейс IRemUnknown. Как уже отмечалось, OXID идентифицирует группу объектов, с которыми можно связаться через данное строковое связывание. Все такие объекты представлены одним объектом OXID и, следовательно, одним IRemUnknown (см. рисунок). Все удаленные вызовы методов всех IUnknown таких объектов вначале обрабатываются интерфейсом IRemUnknown соответствующего объекта OXID.
Интерфейс IRemUnknown аналогичен, но не эквивалентен IUnknown. Как и IUnknown, он содержит 3 метода: RemQueryInterface, RemAddRef и RemRelease. Но в отличие от методов IUnknown они позволяют выполнять групповые запросы. Например, одним вызовом RemQueryInterface можно запросить несколько указателей на интерфейсы нескольких объектов. Аналогично RemAddRef и RemRelease позволяют одновременно увеличивать и уменьшать счетчики ссылок нескольких интерфейсов нескольких объектов (доступ к каждому из которых осуществляется с помощью одной и той же информации связывания).
Клиенты не используют интерфейс IRem Unknown непосредственно. Вместо этого они, как всегда, вызывают методы обычного IUnknown. Реализация этих методов стандартным заместителем группирует вызовы и затем выполняет (минимально возможное количество раз) удаленные вызовы IRemUnknown. Например, вызовы клиентом AddRef и Release обычно не транслируются в вызовы IRemUnknown::RemAddRef и IRemUnknown::RemRelease один к одному. Вместо этого каждый из этих методов IRemUnknown вызывается не более одного раза — после первого вызова AddRef и после последнего вызова Release. Значит, объект могут использовать несколько клиентов, и в то же время его счетчик ссылок может быть равен 1.
И никаких проблем: ведь объект не будет разрушен пока его счетчик ссылок не равен 0. С точки зрения объекта и его клиентов, все работает аналогично локальному случаю, но в распределенной среде это решение гораздо эффективнее.
Хотя оптимизация вызовов IUnknown, выполняемая инфраструктурой СОМ, несомненно полезна, иногда и сам клиент может действовать более разумно. Например, когда ему нужны указатели на несколько интерфейсов данного удаленного объекта, быстрее всего получить их за один вызов. Для этой цели DCOM определяет интерфейс IMultiQI, указатель на который клиент получает обычным способом — вызывая QueryInterface через указатель на любой интерфейс. Но IMultiQI обычно реализуется локальным заместителем, а не удаленным объектом, так что создается иллюзия, что его поддерживает любой объект. Этот простой интерфейс содержит один метод (кроме унаследованных от IUnknown) — QueryMultipIeInterfaces. Клиент может передать ему список идентификаторов интерфейсов и в ответ получить указатель на каждый из них. Вместо того, чтобы заставлять клиента выполнять несколько удаленных вызовов, вся информация получается в результате одного запроса к удаленному объекту.
Непосредственно от класса CObject наследуются ряд классов, которые сами являются базовыми для остальных классов MFC. В первую очередь это класс CCmdTarget, представляющий основу структуры любого приложения. Основной особенностью класса CCmdTarget и классов, наследованных от него, является то, что объекты этих классов могут получать от операционной системы сообщения и обрабатывать их. От класса CCmdTarget наследуется несколько классов, краткое описание которых следует ниже.
Поскольку архитектура Windows-программ основана на принципе сообщений, все эти программы содержат некоторые общие компоненты. Обычно их приходится в явном виде включать в исходный код. Но, к счастью, при использовании библиотеки MFC это происходит автоматически; нет необходимости тратить время и усилия на их написание. Тем не менее, чтобы до конца разобраться, как работает Windows-программа, написанная с использованием MFC, и почему она работает именно так, необходимо в общих чертах понять назначение этих компонентов.
Функция WinMain()
Все Windows-программы начинают выполнение с вызова функции WinMain(). При традиционном методе программирования это нужно делать явно. С использованием библиотеки MFC такая необходимость отпадает, но функция все-таки существует.
Функция окна
Все Windows-программы должны содержать специальную функцию, которая не используется в самой программе, но вызывается самой операционной системой. Эту функцию обычно называют функцией окна, или процедурой окна. Она вызывается Windows, когда системе необходимо передать сообщение в программу. Именно через нее осуществляется взаимодействие между программой и системой. Функция окна передает сообщение в своих аргументах. Согласно терминологии Windows, функции, вызываемые системой, называются функциями обратного вызова. Таким образом, функция окна является функцией обратного вызова.
Помимо принятия сообщения от Windows, функция окна должна вызывать выполнение действия, указанного в сообщении. Конечно, программа не обязана отвечать на все сообщения, посылаемые Windows. Поскольку их могут быть сотни, то большинство сообщений обычно обрабатывается самой системой, а программе достаточно поручить Windows выполнить действия, предусмотренные по умолчанию.
В большинстве Windows-программ задача создания функции окна лежит на программисте. При использовании библиотеки MFC такая функция создается автоматически. В этом заключается одно из преимуществ библиотеки. Но в любом случае, если сообщение получено, то программа должна выполнить некоторое действие.
Хотя она может вызывать для этого одну или несколько API-функций, само действие было инициировано Windows. Поэтому именно способ взаимодействия с операционной системой через сообщения диктует общий принцип построения всех программ для Windows, написанных как с использованием MFC, так и без нее.
Цикл сообщений
Как объяснялось выше, Windows взаимодействует с программой, посылая ей сообщения. Все приложения Windows должны организовать так называемый цикл сообщений (обычно внутри функции WinMain()). В этом цикле каждое необработанное сообщение должно быть извлечено из очереди сообщений данного приложения и передано назад в Windows, которая затем вызывает функцию окна программы с данным сообщением в качестве аргумента. В традиционных Windows-программах необходимо самостоятельно создавать и активизировать такой цикл. При использовании MFC это также выполняется автоматически. Однако важно помнить, что цикл сообщений все же существует. Он является неотъемлемой частью любого приложения Windows.
Процесс получения и обработки сообщений может показаться чересчур сложным, но тем не менее ему должны следовать все Windows-программы. К счастью, при использовании библиотеки MFC большинство частных деталей скрыты от программиста, хотя и продолжают неявно присутствовать в программе.
Класс окна
Как будет показано дальше, каждое окно в Windows-приложении характеризуется определенными атрибутами, называемыми классом окна. (Здесь понятие “класс” не идентично используемому в С++. Оно, скорее, означает стиль или тип.) В традиционной программе класс окна должен быть определен и зарегистрирован прежде, чем будет создано окно. При регистрации необходимо сообщить Windows, какой вид должно иметь окно и какую функцию оно выполняет. В то же время регистрация класса окна еще не означает создание самого окна. Для этого требуется выполнить дополнительные действия. При использовании библиотеки MFC создавать собственный класс окна нет необходимости. Вместо этого можно работать с одним из заранее определенных классов, описанных в библиотеке.
В этом еще одно ее преимущество.
Специфика программ для Windows
Структура Windows-программ отличается от структуры программ других типов. Это вызвано двумя обстоятельствами: во-первых, способом взаимодействия между программой и Windows, описанным выше; во-вторых, правилами, которым следует подчиняться для создания стандартного интерфейса Windows-приложения (т.е. чтобы сделать программу “похожей “ на Windows-приложение).
Цель Windows – дать человеку, который хотя бы немного знаком с системой, возможность сесть за компьютер и запустить любое приложение без предварительной подготовки. Для этого Windows предоставляет дружественный интерфейс пользователя. Теоретически, если пользователь сумел запустить одно Windows-приложение, то он сумеет запустить и любое другое. Конечно, на практике придется немного потренироваться, чтобы научиться использовать большинство программ с максимальной эффективностью. Однако это связано исключительно с тем, что программа делает, а не с тем, как ею пользоваться. Ведь, фактически, значительная часть кода Windows-приложения предназначена именно для организации интерфейса с пользователем.
Хотя создание удобного интерфейса “под Windows” является основной задачей при написании любой Windows-программы, такой интерфейс не создается автоматически. То есть вполне можно написать программу, в которой элементы интерфейса используются неэффективно. Чтобы этого избежать, необходимо целенаправленно применять методику, описанную в данной книге. Только программы, написанные таким способом, будут выглядеть и работать действительно так, как надлежит Windows-программам.
Чтобы отойти от философии создания традиционного Windows-интерфейса, должны быть достаточно веские основания. Иначе пользователи этой программы будут разочарованы. В общем, если программист собирается писать приложения для Windows, то он должен дать пользователям возможность работать с обычным интерфейсом и руководствоваться стандартной методикой разработки.
Типы данных в Windows
В Windows-программах вообще (и в использующих библиотеку MFC в частности) не слишком широко применяются стандартные типы данных из С или С++, такие как int или char*.
Вместо них используются типы данных, определенные в различных библиотечных (header) файлах. Наиболее часто используемыми типами являются HANDLE, HWND, BYTE, WORD, DWORD, UNIT, LONG, BOOL, LPSTR и LPCSTR. Тип HANDLE обозначает 32-разрядное целое, используемое в качестве дескриптора. Есть несколько похожих типов данных, но все они имеют ту же длину, что и HANDLE, и начинаются с литеры Н. Дескриптор – это просто число, определяющее некоторый ресурс. Например, тип HWND обозначает 32-разрядное целое – дескриптор окна. В программах, использующих библиотеку MFC, дескрипторы применяются не столь широко, как это имеет место в традиционных программах. Тип BYTE обозначает 8-разрядное беззнаковое символьное значение, тип WORD – 16-разрядное беззнаковое короткое целое, тип DWORD – беззнаковое длинное целое, тип UNIT - беззнаковое 32-разрядное целое. Тип LONG эквивалентен типу long. Тип BOOL обозначает целое и используется, когда значение может быть либо истинным, либо ложным. Тип LPSTR определяет указатель на строку, а LPCSTR – константный (const) указатель на строку.
Остановить выполнение потока можно с помощью метода SuspendThread класса CWinThread. В остановленном состоянии поток не выполняется. Продолжить выполнение потока можно с помощью метода ResumeThread класса CWinThread.
Каждый поток имеет связанный с ним счетчик остановок. Если этот счетчик равен нулю, значит поток выполняется нормально. При ненулевом значении счетчика поток находится в остановленном состоянии. С каждым вызовом метода SuspendThread значение счетчика остановок увеличивается на единицу. И, наоборот, с каждым вызовом функции ResumeThread значение счетчика остановок уменьшается на единицу. Остановленный поток может продолжить выполнение только после того, как значение счетчика достигнет нуля.
Первоначально OLE была задумана как технология интеграции программных продуктов, входящих в комплект Microsoft Office. Предшественницей OLE является реализованная в Windows технология динамического обмена данными DDE (Dynamic Data Exchange), до сих пор широко применяемая в данной среде. Однако многие разработчики не без оснований считают, что DDE трудно использовать, поскольку это технология низкого уровня. По существу, DDE представляет собой модель взаимодействия процессов - протокол, с помощью которого приложение может организовать канал обмена данными с DDE-сервером, находящимся на той же машине. DDE - это асинхронный протокол. Иными словами, после установления связи вызывающая сторона передает запрос и ожидает возврата результатов. Такой механизм более сложен, чем синхронный вызов функции, так как нужно учитывать вероятность нарушения связи, тайм- ауты и другие ошибки, которые приложение должно распознавать и исправлять. Низкая популярность DDE вынуждала Microsoft искать различные способы его усовершенствования. Для упрощения наиболее сложных аспектов протокола была предложена спецификация DDEML, но этого оказалось недостаточно.
Несмотря на различия между низкоуровневой технологией системных объектов и средствами интеграции компонентов высокого уровня, Microsoft попыталась предоставить разработчикам объединенное решение. В качестве технологии более высокого уровня была реализована OLE 1.0 OLE 1 (Object Linking and Embedding — связывание и внедрение объектов). Она расширила возможности протокола DDE и, используя его как базовый механизм коммуникаций, позволила активизировать встроенный объект в документе, т. е. получить составной документ. Таким образом, OLE 1.0 унаследовала многие проблемы асинхронного протокола. Эта технология имела множество недостатков, а ее компоновка была слишком сложна для пользователей среднего уровня. Кроме того, установленные связи легко нарушались, например, в результате изменения маршрута доступа к файлу связанного объекта.
Первое воплощение OLE — OLE 1 — представляло собой механизм создания и работы с составными документами (compound documents).
С точки зрения пользователя, составной документ выглядит единым набором информации, но фактически содержит элементы, созданные двумя или несколькими разными приложениями. С помощью OLE 1 пользователь мог, например, объединить электронную таблицу, созданную Microsoft Excel, с текстовым документом “производства” Microsoft Word. Идея состояла в том, чтобы документо-ориентированная (document-centric) модель работы с компьютером позволила бы пользователю больше думать об информации и меньше — о приложениях, ее обрабатывающих. Как следует из слов “связывание и внедрение”, составные документы можно создать, либо связав два разных документа, либо полностью внедрив один документ в другой.
OLE 1, как и большинство первых версий программных продуктов, была несовершенна. Архитекторам следующей версии предстояло улучшить первоначальный проект. Вскоре они поняли, что составные документы — лишь частный случай более общей проблемы: как разные программные компоненты должны предоставлять друг другу сервисы? Для решения этой проблемы архитекторы OLE создали группу технологий, область применения которых гораздо шире составных документов. Основу OLE 2 составляет важнейшая из этих технологий — Модель многокомпонентных объектов (Component Object Model — СОМ). Новая версия OLE не только обеспечивает поддержку составных документов лучше, чем первая, но и несомненно идет куда дальше простого объединения документов, созданных в разных приложениях. OLE 2 позволяет по-новому взглянуть на взаимодействие любых типов программ.
Новые возможности многим обязаны СОМ, предоставившей общую парадигму взаимодействия программ любых типов: библиотек, приложений, системного программного обеспечения и др. Вот почему подход, предложенный СОМ, можно использовать при реализации практически любой программной технологии, и его применение дает немало существенных преимуществ.
Благодаря этим преимуществам, СОМ скоро стал частью технологий, не имеющих никакого отношения к составным документам. Однако в Microsoft хотели сохранить общее имя для всей группы технологий, в основе которых лежит СОМ.
Компания решила сократить название Object Linking and Embedding до OLE — эта комбинация более не рассматривалась как аббревиатура — и опустить номер версии.
В начале 1996 года Microsoft ввела в оборот новый термин — ActiveX. Сначала он относился к технологиям, связанным с Интернетом, и приложениям, выросшим из него, вроде WWW (World Wide Web). Поскольку большинство разработок Microsoft в данной области было основано на СОМ, то и ActiveX была непосредственно связана с OLE. Однако очень скоро новый термин стал захватывать территории, традиционно принадлежавшие OLE, и вот теперь все вернулось на круги своя: OLE, как встарь, обозначает только технологию создания составных документов связыванием и внедрением, а разнообразные технологии на основе СОМ, ранее объединенные под именем OLE, собраны под знаменем ActiveX. А некоторые технологии, название которых содержало слово "OLE" даже перекрестили: теперь это технологии ActiveX. Новые технологии на основе СОМ, которым раньше полагался ярлык "OLE", теперь частенько получают пометку "ActiveX".
Что такое отображаемые файлы? Обычно механизм виртуальной памяти позволяет операционной системе отображать несуществующую память в дисковый файл, называемый страничным файлом. Можно посмотреть на это с другой стороны и рассмотреть механизм виртуальной памяти как метод ссылок на содержимое файла, называемого страничным файлом, через указатели так, как будто страничный файл является объектом памяти. Другими словами, этот механизм отображает содержимое страничного файла в адреса памяти. Если это можно сделать со страничным файлом, то почему нельзя с другими файлами? Отображаемые в память файлы представляют естественное расширение механизма управления виртуальной памятью.
Файловое отображение можно создать с помощью функции CreateFileMapping. Для открытия существующего отображения с определенным именем приложения могут использовать функцию OpenFileMapping. Функция MapViewOfFile отображает часть файла в блок виртуальной памяти.
Особенностью отображаемых в память файлов является их совместное использование разными приложениями, т.е., если два приложения открыли файловое отображение с одним и тем же именем, то они, по сути, создали блок разделяемой памяти.
Не излишне ли использовать дисковый файл, если необходимо лишь передать несколько байт между приложениями? В действительности нет необходимости явно открывать и использовать дисковый файл для получения отображения в памяти. Приложения могут передать специальное значение дескриптору 0хFFFFFFFF в функцию CreateFileMapping для получения отображения непосредственно в системный страничный файл. Это, по сути, создает блок разделяемой памяти.
Для изменения текста в самом первом индикаторе панели состояния, который имеет идентификатор ID_SEPARATOR, можно воспользоваться методом SetWindowText. Этот метод определен в классе CWnd, и его можно использовать, так как класс панели состояния наследуется от класса CWnd. Строку, которую надо вывести в панели состояния, следует передать через параметр этого метода.
Метод SetTextWindow изменяет текст в панели состояния, передавая ему сообщение WM_SETTEXT. Самый первый индикатор панели состояния, который имеет идентификатор ID_SEPARATOR, отличается от остальных индикаторов панели состояния. В нем отображаются подсказки для выбранных строк меню, кнопок панели управления и в некоторых других случаях. Фактически этот индикатор используется различными объектами библиотеки MFC для отображения своего состояния или кратких подсказок.
Объекты MFC устанавливают текст первого индикатора, который имеет идентификатор ID_SEPARATOR, непосредственного передавая окну панели состояния сообщение WM_SETTEXT. Отсюда, кстати, следует один вывод: текст, который программист самостоятельно выводит методом SetWindowText, может быть изменен в любой момент без ведома программиста. Чтобы исправить такое положение, можно наследовать класс, в котором определить обработчик для сообщения WM_SETTEXT. Этот обработчик, например, может полностью игнорировать сообщения WM_SETTEXT или обрабатывать их только в особых случаях.
Если необходимо изменить текст не только в первом индикаторе, но и в других индикаторах панели состояния, можно воспользоваться методом SetPaneText, который определен в классе CStatusBar. Текст, который отображается в индикаторе в данный момент, можно определить при помощи метода GetPaneText, определенного в класса CStatusBar:
Еще один метод изменения индикаторов панели состояния основан на обработке сообщения ON_UPDATE_COMMAND_UI. Во время, когда приложение “простаивает”, сообщение ON_UPDATE_COMMAND_UI передается для всех индикаторов панели состояния (также как и для всех строк меню и всех кнопок панели управления).
Сервер, реализующий один или несколько объектов СОМ, доступных клиентам на других машинах, можно рассматривать как экспортер объектов (object exporter). Разрешая удаленным клиентам доступ к своим объектам, сервер в некотором смысле "экспортирует" эти объекты, обеспечивая их межмашинное использование. Если сервер — однопоточный процесс с одним или несколькими объектами, то в качестве экспортера выступает сервер в целом. Если же сервер — многопоточный процесс с одной или несколькими комнатами (apartment), содержащими различные объекты, то экспортером является каждая из комнат. В любом случае каждому экспортеру объектов присваивается 8-байтовое значение — идентификатор экспортера объектов (OXID — Object Exporter Identifier). Заметьте: у одного процесса с несколькими комнатами может быть несколько OXID.
Для доступа к удаленному объекту клиент вначале должен получить его OXID (как это делается, объясняктся в следующем разделе.) Имея OXID, клиент может использовать разрешатель OXID для отображения OXID в информацию связывания с объектом. На любой машине, поддерживающей DCOM, имеется разрешатель OXID (OXID resolver), и каждый разрешатель OXID поддерживает интерфейс lObjectExporter (название этого интерфейса, уходящее своими корнями в историю DCOM, несколько неудачно, так как он не реализуется экспортером объектов). Несмотря на свое СОМ-подобное имя, IObjectExporter не является интерфейсом СОМ. На самом деле это интерфейс RPC, и обращение к нему выполняется через вызовы чистого RPC, а не вызовы ORPC. В его составе 3 "метода": ResolveOXID, SimplePing и ComplexPing.
Каждый разрешатель OXID поддерживает таблицу OXID и их строковых связывании. Как минимум, данная таблица содержит запись для каждого исполняющегося на данной машине объекта, имеющего клиента вне своего процесса или комнаты. Она обычно включает и некоторые OXID и строковые связывания для объектов, исполняющихся на других машинах. Чтобы понять почему, рассмотрим, как осуществляется передача указателей на интерфейсы между машинами.
В отличие от статических библиотек, которые, по существу, становятся частью кода приложения, библиотеки динамической компоновки в 16-разрядных версиях Windows работали с памятью несколько иначе. Под управлением Win 16 память DLL размещалась вне адресного пространства задачи. Размещение динамических библиотек в глобальной памяти обеспечивало возможность совместного использования их различными задачами.
В Win32 библиотека DLL располагается в области памяти загружающего ее процесса. Каждому процессу предоставляется отдельная копия "глобальной" памяти DLL, которая реинициализируется каждый раз, когда ее загружает новый процесс. Это означает, что динамическая библиотека не может использоваться совместно, в общей памяти, как это было в Winl6.
И все же, выполнив ряд замысловатых манипуляций над сегментом данных DLL, можно создать общую область памяти для всех процессов, использующих данную библиотеку.
Допустим, имеется массив целых чисел, который должен использоваться всеми процессами, загружающими данную DLL. Это можно запрограммировать следующим образом:
#pragma data_seg(".myseg") int sharedlnts[10] ; // другие переменные общего пользования #pragma data_seg() #pragma comment(lib, "msvcrt" "-SECTION:.myseg,rws");
Все переменные, объявленные между директивами #pragma data_seg(), размещаются в сегменте .myseg. Директива #pragma comment () — не обычный комментарий. Она дает указание библиотеке выполняющей системы С пометить новый раздел как разрешенный для чтения, записи и совместного доступа.
Класс CFindReplaceDialog предназначен для управления диалоговыми окнами Find и Replace. Диалоговая панель Find используется для поиска известных строк в документе приложения, а панель Replace позволяет замену одной строки на другую.
Важным отличием диалоговых панелей Find и Replace от других стандартных диалоговых панелей является то, что они представляют собой немодальные диалоговые панели. Поэтому процесс создания этих панелей значительно отличается от процесса создания стандартных панелей для выбора цвета, шрифта и имен файла.
Класс CPrintDialog можно использовать для создания двух видов диалоговых панелей, предназначенных для печати документов и выбора форматов документов. Кроме класса CPrintDialog можно также использовать класс CPageSetupDialog. Он позволяет создать диалоговую панель для выбора формата документа, имеющую несколько иной вид.
В приложениях, подготовленных с использованием средств MFC AppWizard и построенные по модели документ-облик, по умолчанию встроена возможность вывода редактируемого документа на печать.
В меню File такого приложения находятся три строки (Print, Print Preview и Print Setup), которые управляют процессом печати документов, подготовленных в приложении. Чтобы распечатать документ, достаточно выбрать из меню File строку Print. На экране появится диалоговая панель Print. В ней можно выбрать печатающее устройство для печати документов (группа Name), указать, будет печататься весь документ либо его часть (группа Print range), а также сколько копий документа будет напечатано (группа Copies). Также можно настроить различные характеристики печатающего устройства, если нажать кнопку Properties в группе Printer.
Если требуется определить только печатающее устройство и формат документа, из меню File следует выбрать строку Printer Setup. В группе Printer можно указать печатающее устройство и настроить его соответствующим образом. Группа Paper задает формат бумаги и режим подачи бумаги в печатающее устройство. Группа Orientation включает только один переключатель, определяющий ориентацию бумаги. Он принимает положение Portrait для вертикальной ориентации изображения на бумаге (режим "портрет") или Landscape для горизонтальной ориентации изоборажения на бумаге (режим "ландшафт").
Строка Print Preview меню File выбирается для предварительного просмотра документа перед печатью. При этом главное окно приложения изменит свой внешний вид и можно будет просмотреть, как будет выглядеть документ после печати.
Если не требуется выполнять специфическую обработку документа перед печатью, то вряд ли понадобится самостоятельное добавление программного кода, отвечающего за процесс печати. Просто следует отметить, что процедура создания панелей, связанных с печатью документа, практически ничем не отличается от создания выше описанных стандартных диалоговых панелей.
Чтобы отобразить на экране стандартную диалоговую панель выбора цвета, надо создать объект класса CColorDialog, а затем вызвать метод DoModal. При создании объекта класса СColorDialog используется следующий конструктор:
CColorDialog(COLORREF clrInit=0,DWORD dwFlags=0,CWnd* pParentWnd=NULL);
Все параметры конструктора необязательны, однако в некоторых случаях использование этих параметров может помочь.
Первый параметр clrInit позволяет указать цвет, выбранный по умолчанию сразу после открытия диалоговой панели. Если параметр не будет указан, в качестве цвета, выбранного по умолчанию, будет использоваться черный цвет.
Параметр dwFlags содержит набор флагов, управляющих диалоговой панелью выбора цвета. При помощи него блокировать или разрешать работу некоторых элементов управления диалоговой панели выбора цвета. Если при создании объекта класса CColorDialog не указать параметр dwFlags, тем не менее можно выполнить настройку диалоговой панели, обратившись непосредственно к элементу m_cc данного класса. Параметр dwFlags, указанный в конструкторе, используется для инициализации m_cc. Изменения в элемент m_cc должны быть внесены до того, как панель будет отображаться на экране.
Последний параметр pParentWnd можно использовать, чтобы указать родительское окно диалоговой панели.
Методы класса CСolorDialog
Чтобы вывести диалоговую панель выбора цвета на экран, необходимо использовать метод DoModal. После отображения панели на экране пользователь может выбрать из нее цвет и нажать кнопки OK или Cancel для подтверждения выбора цвета или отказа от него. Когда диалоговая панель закрывается, метод DoModal возвращается значения IDOK и IDCANCEL, в зависимости от того, какую кнопку нажал пользователь:
CColorDialog dlgColor; int iResult=dlgColor.DoModal();
На экране появится стандартная диалоговая панель выбора цвета Color. В верхней половине диалоговой панели расположены 48 прямоугольников, имеющих различные цвета. Они представляют так называемые основные цвета (Basic colors).
Можно выбрать один из этих цветов и нажать кнопку OK. После того, как диалоговая панель закрыта (метод DoModal завершил свою работу), можно воспользоваться методами класса CColorDialog, чтобы узнать цвета, выбранные пользователем.
Для определения цвета, выбранного пользователем, можно обратиться к методу GetColor класса CColorDialog. Данный метод возвращает значение COLORREF, соответствующее выбранному цвету.
Если пользователю недостаточно основных цветов, представленных в диалоговой панели Color, он может выбрать до 16 дополнительных цветов. Для этого он должен нажать кнопку DefineCustom Colors. Диалоговая панель изменит свой внешний вид - появятся дополнительные органы управления, позволяющие выбрать любой из 16 777 216 цветов. Когда цвет выбран, нужно нажать кнопку Add Custom Colors. Выбранный цвет будет добавлен к дополнительным цветам (Custom colors) - один из свободных прямоугольников окрасится соответствующим цветом.
При помощи метода GetSavedCustomColors класса CColorDialog можно определить дополнительные цвета, выбранные пользователем в диалоговой панели Color. Этот метод возвращает указатель на массив из 16 элементов типа COLORREF. Каждый элемент массива описывает один дополнительный цвет.
Когда диалоговая панель Color отображается приложением первый раз, все прямоугольники, отображающие дополнительные цвета, имеют белый цвет. Дополнительные цвета, выбранные пользователем, сохраняются во время работы приложения. После перезапуска приложения дополнительные цвета сбрасываются.
Среди стандартных диалоговых панелей, для которых в библиотеке MFC создан специальный класс, есть панели для работы с файловой системой - Open и Save As. Диалоговая панель Open позволяет выбрать один или несколько файлов и открыть их для дальнейшего использования. Диалоговая панель Save As позволяет выбрать имя файла для записи в него документа.
Для управления диалоговыми панелями Open и Save As предназначен один класс CFileDialog. Рассмотрим конструктор класса CFileDialog более подробно:
CFileDialog(BOOL bOpenFileDialog, LPCTSTR lpszDefExt=NULL,LPCTSTR lpszFileName=NULL, DWORD dwFlags=OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter=NULL,CWnd* pParentWnd=NULL);
Объекты класса CFileDialog представляют диалоговые панели Open или Save As в зависимости от параметра bOpenFileDialog. Если параметр bOpenFileDialog содержит значение TRUE, то создается объект, управляющий диалоговой панелью Open, а если FALSE - диалоговой панелью Save As.
Параметр bOpenFileDialog является единственным обязательным параметром, который необходимо указать. Остальные параметры конструктора класса CFileDialog задают различные режимы работы панели и могут не указываться.
Чтобы создать объект класса CFileDialog, представляющий диалоговую панель для открытия файлов (mFileOpen), и объект, представляющий диалоговую панель для сохранения файлов (mFileSaveAs), можно воспользоваться следующими вызовами конструктора класса:
CFileDialog mFileOpen(TRUE); // для панели открытия файлов CFileDialog mFileSaveAs(FALSE); // для панели сохранения файла
Во многих случаях имена файлов, которые нужно открыть или закрыть, имеют определенное расширение. Параметр lpszDefExt позволяет задать расширение файлов, используемое по умолчанию. То есть, если пользователь при определении имени файла не укажет расширение, имени файла автоматически присваивается расширение, принятое по умолчанию. Если при определении свойств диалоговой панели программист присвоит параметру lpszDefExt значение NULL, то расширение файлов должно задаваться пользователем явно.
Стандартная диалоговая панель Font предназначена для выбора шрифта. Эта панель отображает список шрифтов, установленных в системе, и позволяет выбрать название шрифта, его начертание и другие параметры.
Для управления диалоговой панелью Font в библиотеку классов MFC включен класс CFontDialog. Методы этого класса можно использовать для отображения панели Font и определения характеристик шрифта, выбранного пользователем. Конструктор класса CFontDialog:
CFontDialog(LPLOGFONT lplfInitial=NULL, DWORD dwFlags=CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter,CWnd* pParentWnd=NULL);
Все параметры конструктора являются необязательными. Настройка стандартной панели выбора шрифта, которая выполняется конструктором класса CFontDialog по умолчанию, удовлетворяет большинству пользователей.
Параметр lplfInitial является указателем на структуру LOGFONT, описывающую логический шрифт. Если этот параметр используется, то в диалоговой панели по умолчанию будет выбран шрифт, наиболее соответствующий шрифту, описанному в структуре LOGFONT.
Параметр dwFlags задает набор флагов, управляющий различными режимами работы панели. Например, флаг CF_EFFECTS позволяет пользователю создавать подчеркнутые и перечеркнутые буквы, определять цвет букв, а флаг CF_SCREENFONTS - разрешает выбирать только экранные шрифты.
Через параметр pdcPrinter можно передать конструктору контекст отображения принтера, шрифты которого будут представлены в диалоговой панели Font. Данный параметр используется только в том случае, если в параметре dwFlags указаны флаги CF_PRINTERFONTS или CF_BOTH.
Через параметр pParentWnd можно указать родительское окно для диалоговой панели Font.
Методы класса CFontDialog
Для отображения диалоговой панели Font предназначен виртуальный метод DoModal. Если пользователь выбрал шрифт и нажал кнопку OK, метод DoModal возвращает идентификатор IDOK, если пользователь отменил выбор шрифта, метод DoModal возвращает идентификатор IDCANCEL:
CFontDialog dlgFont(); int iResult=dlgFont.DoModal();
Объекты состоят из методов и данных, и многим объектам необходимо сохранять свои данные в течение периодов неактивности. На профессиональном жаргоне, объекту необходимо сделать свои данные перманентными (persistent), что обычно означает запись их на диск. СОМ-объекты достигают этого разными путями. Один из наиболее широко применяемых — структурированное хранилище (Structured Storage).
Чтобы понять идею структурированного хранилища, вначале рассмотрим, как приложения сохраняют свои данные в обычных файлах. Традиционные файловые системы обеспечивают совместное использование приложениями одного дискового устройства без конфликтов между ними. Каждое приложение работает со своими собственными файлами и, может быть, даже с собственными подкаталогами независимо от того, чем заняты в тот же момент другие приложения. Приложениям не требуется взаимодействовать друг с другом, чтобы сохранить свои данные, так как у каждого отдельная область для хранения.
Однако в СОМ ситуация сложнее. Так как СОМ обеспечивает совместную работу разных типов программ с помощью одной модели, то независимо разработанный СОМ-объект может стать частью чего-то, что пользователь будет считать одним приложением, и в то же время объекту по-прежнему будет необходимо хранить свои данные на диске отдельно. Каждый СОМ-объект мог бы использовать отдельный файл, но для пользователя приложения объекты невидимы — ведь это одно приложение! — и необходимость следить за большим количеством файлов вряд ли бы ему понравилась.
То, что нам нужно, — это способ совместного использования одного файла несколькими СОМ-объектами. Такую возможность и предоставляет структурированное хранилище. Создавая, по сути дела, файловую систему внутри каждого файла, структурированное хранилище предоставляет каждому компоненту, составляющему некоторое приложение, собственный отдельный кусок пространства хранилища, собственные “файлы”. С точки зрения пользователя, файл только один. Однако с точки зрения приложения, каждый компонент имеет собственную область для хранения данных, и все такие области находятся внутри одного дискового файла.
Итак, OLE - это набор стандартов для связи и внедрения объектов при создании компонентов программного обеспечения. Одним из стандартов OLE является спецификация модели составного объекта (или COM), основа для бинарных соединений между компонентами.
Начав скромно, как способ создания составных документов, СОМ развилась в фундаментальную основу прикладного и системного программного обеспечения. СОМ получила такое широкое применение потому, что определяемая ею архитектура предоставления сервисов — привлекательное решение массы проблем. Учитывая ее универсальность и очевидные преимущества, описанные здесь применения СОМ, по всей вероятности, только начало. Хотя общая марка, под которой выступают технологии на основе СОМ, с течением времени изменялась от OLE к ActiveX, с чисто технической точки зрения, это не имеет значения. Дело не в названии, а в тех преимуществах СОМ и ее приложений, что продолжают повсеместно проявляться во всей этой части мира информатики.
OLE позволяет увеличить степень интеграции между программными модулями и служит для создания множества взаимозаменяемых компонентов программного обеспечения. Набор услуг, которые предлагает OLE, не постоянен. Microsoft постоянно модернизирует и расширяет операционную систему Windows, и точно также она готова развивать OLE, чтобы адаптировать к широкому диапазону требованию по интеграции приложений.
От класса CCmdTarget наследуется класс CWinThread, представляющий подзадачи приложения. Простые приложения, которые будут рассматриваться дальше, имеют только одну подзадачу. Эта подзадача, называемая главной, представляется классом CWinApp, наследованным от класса CWinThread.
Если проект динамической библиотеки создан с помощью AppWizard и .def-файл модифицирован соответствующим образом — этого достаточно. Если же файлы проекта создаются вручную или другими способами без помощи AppWizard, в командную строку редактора связей следует включить параметр /DLL. В результате вместо автономного выполняемого файла будет создана библиотека DLL.
Если в .def-файле есть строка LIBRART, указывать явно параметр /DLL в командной строке редактора связей не нужно.
Для MFC предусмотрен ряд особых режимов, касающихся использования динамической библиотекой библиотек MFC. Этому вопросу посвящен следующий раздел.
Для того, чтобы пользователь мог сам перемещать панель управления с одной границы экрана на другую или разместить ее в отдельном окне, необходимо сделать следующие действия:
Разрешить перемещение панели управление для окна, которое содержит панель управления, вызовом метода EnableDocking данного окна (этот метод является элементом класса CFrameWnd).
Разрешить такое перемещение для самой панели управления методом EnableDocking панели управления (этот метод является элементом класса CControlBar).
Переместить панель управления к одной из сторон приложения или вывести ее в отдельном окне. Для этого необходимо вызвать метод DockControlBar или FloatControlBar данного окна приложения (эти методы являются элементами класса CFrameWnd).
Если не выполнить хотя бы один пункт из этих трех перечисленных выше, панель управления будет жестко привязана к одной из границ окна.
Все технологии OLE и ActiveX, описанные ниже, построены на основании, обеспеченном СОМ. Итак, что же такое СОМ? Чтобы ответить на этот вопрос, зададимся сначала другим: "Каким образом одна часть программного обеспечения должна получать доступ к сервисам, предоставляемым другой частью? " На сегодняшний день ответ зависит от того, что представляют собой эти части:
Приложения, например, скомпонованные с библиотекой, могут пользоваться ее сервисами, вызывая функции из этой библиотеки.
Приложение также может использовать сервисы другого — являющегося совершенно отдельным процессом. В этом случае два таких локальных процесса взаимодействуют посредством некоего механизма связи, который обычно требует определения протокола между этими приложениями (набор сообщений, позволяющий одному приложению выдавать запросы, а другому соответствующим образом отвечать на них).
Еще пример — приложение, использующее сервисы операционной системы. Здесь приложение обычно выполняет системные вызовы, обрабатываемые операционной системой.
Наконец, приложению могут понадобиться сервисы, предоставляемые программным обеспечением, выполняемым на другой машине, доступ к которой осуществляется по сети. Получить доступ к таким сервисам можно множеством способов, таких как обмен сообщениями с удаленным приложением или вызовы удаленных процедур.
В принципе проблема одна: одна часть программного обеспечения должен получить доступ к сервисам, предоставляемым другой частью. Но в каждом отдельном случае механизм доступа разный: вызовы локальных функций, передача сообщения средствами связи между процессами, системные вызовы (которые с точки зрения программиста выглядят практически так же, как и вызовы функций) или какая-то разновидность сетевых коммуникаций. Зачем все это? Не проще ли определить один общий способ доступа ко всем видам программных сервисов независимо от способа их реализации?
Этим и занимается СОМ. Она определяет стандартный механизм, с помощью которого одна часть программного обеспечения предоставляет свои сервисы другой и который работает во всех описанных выше случаях. Общая архитектура сервисов в библиотеках, приложениях, системном и сетевом программном обеспечении позволяет СОМ изменить подход к созданию программ.
Объекты различных классов обрабатывают командные сообщения по-разному. Например, объекты, представляющие главное окно приложения, сначала предоставляют возможность обработать полученное сообщение другим объектам. Только если сообщение остается необработанным, просматривается таблица сообщений класса главного окна приложения. Если и здесь сообщение не обрабатывается, оно направляется другим объектам приложения.
Подавляющее большинство приложений, созданных на основе MFC, использует ряд стандартных командных сообщений, как правило, соответствующих элементам меню или кнопкам панели управления. К ним относятся командные сообщения для завершения работы приложения, создания нового документа, открытия документа, записанного на диске, сохранения документа на диске, вызова справочной системы, управления текстовым редактором и т.д. За каждым таким сообщением зарезервирован отдельный идентификатор.
В отдельных случаях может понадобиться изменить порядок, в котором сообщения передаются для обработки объектам приложения. В этом случае необходимо переопределить виртуальный метод OnCmdMsg. Этот метод первоначально определен в классе CCmdTarget и переопределен в классах CView и CDocument.
В MFC определены два типа потоков: интерфейсные и рабочие. Интерфейсный поток способен принимать и обрабатывать сообщения. Говоря языком MFC, интерфейсные потоки содержат канал сообщений. Главный поток MFC-программы (начинающийся при объявлении объекта класса CWinApp) является интерфейсным потоком. Рабочие потоки не принимают и не обрабатывают сообщения. Они обеспечивают дополнительные пути выполнения задачи внутри интерфейсного потока.
В MFC потоковая многозадачность реализуется с помощью класса CWinThread. Кстати, что производным от него является класс CWinApp, формирующий поток приложения.
При использовании классов, отвечающих за работу в многозадачном режиме, в программу следует включать стандартный библиотечный файл afxmt.h.
При создании многопотоковых программ наиболее часто используются именно рабочие потоки - необходимость в нескольких каналах сообщений возникает достаточно редко, однако во многих приложениях используются вспомогательные потоки, позволяющие вести фоновую обработку данных. Сосредоточимся поэтому на рабочих потоках (важно понимать, что на уровне API и рабочие, и интерфейсные потоки обрабатываются одинаково; различие между ними существует только в иерархии классов MFC.
Одна из основных задач СОМ — продвижение повсеместного и эффективного повторного использования существующего кода. Позволяя создавать повторно применимые компоненты с четко определенными интерфейсами, СОМ обеспечивает для этой цели инфраструктуру.
Многие, если не большинство, объектно-ориентированные технологии в качестве основного механизма повторного использования существующего кода применяют наследование реализации (когда новый объект наследует фактическую реализацию методов существующего объекта). Однако создатели СОМ полагают, что такой тип наследования является непрактичным для объектной системы, предназначенной для крайне неоднородной среды. (Отсутствие в СОМ наследования реализации ни в коей мере не оказывает влияния на использование этой техники в поддерживающих ее языках программирования типа C++. Реализации СОМ-объектов могут использовать наследование реализации как обычно. Не поддерживается лишь наследование реализации от другого СОМ-объекта. Это не противоречие: C++ — язык реализации объектов, тогда как СОМ — технология, позволяющая создавать компонентное программное обеспечение и многое другое.)
Несмотря на изоляцию, обеспечиваемую интерфейсами, изменения базовых объектов могут вызвать непредсказуемые эффекты в объектах, наследующих от них реализацию. Это может стать проблемой в мире, где базовые объекты и объекты, наследующие от них, создаются, выпускаются и обновляются независимо. СОМ предоставляет два других механизма повторного применения: включение и агрегирование.
Включение и агрегирование — простые концепции. Обе предоставляют способ повторного применения, и в основе обеих лежит некоторая взаимосвязь объектов. В терминологии СОМ "внешним" (outer) называется объект, повторно использующий сервисы "внутреннего" (inner). Внешний объект выступает просто как клиент внутреннего, либо их взаимосвязь может быть несколько более тесной.
Как уже упоминалось, MFC – это базовый набор (библиотека) классов, написанных на языке С++ и предназначенных для упрощения и ускорения процесса программирования для Windows. Библиотека содержит многоуровневую иерархию классов, насчитывающую около 200 членов. Они дают возможность создавать Windows-приложения на базе объектно-ориентированного подхода. С точки зрения программиста, MFC представляет собой каркас, на основе которого можно писать программы для Windows.
Библиотека MFC разрабатывалась для упрощения задач, стоящих перед программистом. Как известно, традиционный метод программирования под Windows требует написания достаточно длинных и сложных программ, имеющих ряд специфических особенностей. В частности, для создания только каркаса программы таким методом понадобится около 75 строк кода. По мере же увеличения сложности программы ее код может достигать поистине невероятных размеров. Однако та же самая программа, написанная с использованием MFC, будет примерно в три раза меньше, поскольку большинство частных деталей скрыто от программиста.
Одним из основных преимуществ работы с MFC является возможность многократного использования одного и того же кода. Так как библиотека содержит много элементов, общих для всех Windows-приложений, нет необходимости каждый раз писать их заново. Вместо этого их можно просто наследовать (говоря языком объектно-ориентированного программирования). Кроме того, интерфейс, обеспечиваемый библиотекой, практически независим от конкретных деталей, его реализующих. Поэтому программы, написанные на основе MFC, могут быть легко адаптированы к новым версиям Windows (в отличие от большинства программ, написанных обычными методами).
Еще одним существенным преимуществом MFC является упрощение взаимодействия с прикладным программным интерфейсом (API) Windows. Любое приложение взаимодействует с Windows через API, который содержит несколько сот функций. Внушительный размер API затрудняет попытки понять и изучить его целиком. Зачастую даже сложно проследить, как отдельные части API связанны друг с другом! Но поскольку библиотека MFC объединяет (путем инкапсуляции) функции API в логически организованное множество классов, интерфейсом становится значительно легче управлять.
Поскольку MFC представляет собой набор классов, написанных на языке С++, поэтому программы, написанные с использованием MFC, должна быть в то же время программами на С++. Для этого необходимо владеть соответствующими знаниями. Для начала необходимо уметь создавать собственные классы, понимать принципы наследования и уметь переопределять виртуальные функции. Хотя программы, использующие библиотеку MFC, обычно не содержат слишком специфических элементов из арсенала С++, для их написания тем не менее требуются солидные знания в данной области.
Замечание. Небольшое число классов, определенных в библиотеке, не связанно непосредственно с программированием под Windows. Это, в частности, классы, предназначенные для создания строк, управления файлами и обработки особых ситуаций. Иногда называемые классами общего назначения, они могут использоваться как Windows-, так и не- Windows-приложениями.
Рассмотрим преимущества использования мастеров в процессе создания приложений. Прежде всего, нужно отметить, что создание проекта - это не только творчество, но и большой объем технической работы, требующей внимания и аккуратности.
Например, все Windows-приложения имеют достаточно общую структуру, и, следовательно, можно построить некоторые шаблонные заготовки, подходящие для того или иного типа проектов. Построению таких заготовок способствует то, что приложения, создаваемые на основе MFC, строятся из элементов фиксированных классов. Логическим развитием этой идеи было введение специальных классов и специальной архитектуры построения приложения, которая подходила бы широкому классу приложений. О такой архитектуре уже упоминалось, когда речь шла о библиотеке MFC, - это архитектура Document-View. Она является основной, но не единственной при построении проектов в среде Visual C++.
Суть этой архитектуры в том, что работу многих приложений можно рассматривать как обработку документов. При этом можно отделить сам документ, отвечающий за представление и хранение данных, от образа этого документа, видимого на экране и допускающего взаимодействие с пользователем, который просматривает и (или) редактирует документ. В соответствии с этой архитектурой библиотека MFC содержит два семейства классов, производных от базовых классов CDocument и CView.
В результате появилась двухэтапная технология создания проектов. Вначале создается некая заготовка проекта с общими свойствами, подходящими для многих проектов этого типа. На втором этапе производится уже настройка, учитывающая специфику задачи. Для каждого этапа фирма Microsoft разработала свое инструментальное средство.
Начальная заготовка - остов приложения - создается в диалоге с пользователем инструментальным средством AppWizard. В процессе диалога пользователь определяет тип и характеристики проекта, который он хочет построить. Определив, какие классы из MFC необходимы для этого проекта, AppWizard строит остовы всех нужных производных классов. Построенный AppWizard остов приложения содержит все необходимые файлы для создания стартового приложения, которое является законченным приложением и обладает разумными функциональными свойствами, общими для целого класса приложений. Естественно, никаких специфических для данного приложения свойств остов не содержит. Они появятся на следующем этапе, когда программист начнет работать с остовом, создавая из заготовки свое собственное приложение. Тем не менее стартовое приложение можно транслировать и запускать на исполнение.
Термин остов (приложения, класса, функции) применяется для заготовок, создаваемых инструментальными средствами AppWizard и ClassWizard. Нужно подчеркнуть - остов приложения и каркас приложения - разные понятия.
Создаваемый остов приложения составлен так, что в дальнейшей работе с проектом можно использовать другое инструментальное средство - ClassWizard (мастер классов).
Все, что упрощает сложный процесс создания больших программ, — хорошо. Соглашения, определенные СОМ, достигают этого несколькими путями.
СОМ предоставляет удобный способ структурирования сервисов, предоставляемых разными фрагментами программного обеспечения. Разработчик может вначале организовать проект в виде СОМ-объектов, а затем определить интерфейсы каждого объекта. Это одно из традиционных преимуществ объектно-ориентированного подхода к проектированию. Но СОМ идет дальше, позволяя разработчикам создавать программные компоненты, которые можно распространять и повторно использовать разными путями.
Второе преимущество СОМ — последовательность. Общий подход к созданию всех типов программных сервисов в СОМ упрощает проблемы, с которыми сталкиваются разработчики. Находится ли нужное программное обеспечение в библиотеке, в другом процессе, является ли частью операционной системы, доступ к нему всегда осуществляется единообразно. У последовательности есть и побочный эффект: СОМ сглаживает различия между системным и прикладным программным обеспечением. Если работать со всеми компонентами, как с объектами СОМ, то не чувствуется между этими двумя типами программного обеспечения существенных различий, которые обычно весьма ощутимы. Теперь можно разрабатывать приложения, использующие доступные в данной среде программные сервисы, независимо от того, чем эти сервисы являются и кто их предоставляет.
В дополнение к этому СОМ безразличен язык программирования. СОМ определяет двоичный интерфейс, который должны поддерживать объекты, поэтому объекты СОМ можно создавать на любом языке, способном поддерживать данный интерфейс. Затем обращаться к методам этих объектов можно будет на любом языке, позволяющем осуществлять вызовы данного двоичного интерфейса. Ни объект, ни его клиент не знают — да и зачем им это? — на каком языке написан другой. Правда, некоторые языки лучше подходят СОМ, однако сама по себе СОМ пытается быть независимой от языка.
Еще одно преимущество СОМ вытекает из ее подхода к одной из сложнейших проблем разработки и установки программ — контролю версий (versioning) — т.е.
как заменить текущую версию программы на новую, с дополнительными возможностями, не повредив существующим клиентам старой версии? Способность СОМ-объекта поддерживать более одного интерфейса — ключ к решению этой проблемы. Клиент объекта СОМ должен получить указатель нужного ему интерфейса. Дополнительные возможности новой версии объекта можно ввести через новый интерфейс. Старые интерфейсы не изменяются (фактически СОМ запрещает изменение существующих интерфейсов), так что использующие их клиенты не затрагиваются. Причем старые клиенты никогда не запросят указатель на новые интерфейсы. А вот у новых клиентов имеется достаточная информация, чтобы запрашивать новые интерфейсы, реализующие дополнительные возможности; таким образом, новая версия повлияет только на новых клиентов.
СОМ решает и другую сторону этой проблемы — когда клиент ожидает, что объект поддерживает некоторую функциональность, но тот еще не обновлен. Такой клиент запрашивает указатель интерфейса, реализующего новые возможности, но в ответ ничего не получает. СОМ предоставляет ясный способ определить, что объект является не вполне тем, чего ожидает клиент, а потому последний можно написать так, чтобы в этой ситуации он корректно деградировал, а не просто “ломался”. Этот простой и ясный подход, допускающий независимое обновление как клиентов, так и используемых ими объектов, — одно из самых больших достижений СОМ.
Microsoft применяет СОМ в большинстве продуктов; она используется для спецификации расширений Microsoft Windows и Microsoft Windows NT, а также для определения стандартных интерфейсов к различным типам сервисов. Выгоды применения СОМ в разработке всех типов программного обеспечения несомненны.
Самые простые приложения с использованием библиотеки классов MFC можно создавать без применения автоматизированных средств разработки приложений MFC AppWizard. Создадим приложение, отображающее на экране маленькую диалоговую панель, которая содержит строку текста. В этом приложении используется единственный класс, наследованный от базового класса CWinApp. Приведем исходный текст приложения:
Файл first.cpp #include <afxwin.h> // Включаемый файл для MFC
// Класс CFirstApp - главный класс приложения. // Наследуется от базового класса CWinApp. class CFirstApp:public CWinApp { public: // Переопределение метода InitInstance, // предназначенного для инициализациии приложения. virtual BOOL InitInstance(); };
// Создание объекта приложения класса CFirstApp. CFirstApp theApp;
// Метод InitInstance // Переопределение виртуального метода InitInstance класса CWinApp. // Он вызывается каждый раз при запуске приложения. BOOL CFirstApp::InitInstance() { AfxMessageBox("First MFC-application"); return FALSE; }
В этом приложении определен только один класс - CFirstApp, наследованный от базового класса CWinApp. В класс CFirstApp входит метод InitInstance. Кроме того, определена одна глобальная переменная - theApp.
Для использования в приложении классов или функций библиотеки MFC необходимо включить эту библиотеку в проект приложения. Программные коды библиотеки классов MFC могут использоваться приложением двумя разными способами. Код библиотеки MFC либо непосредственно записывается в выполняемый файл приложения, либо вызывается по мере необходимости из отдельной dll-библиотеки.
Использование для приложения DLL-библиотеки немного ускоряет процесс построения проекта и позволяет создавать выполняемые файлы значительно меньшего размера. Однако сам по себе такой выполняемый файл работать не будет. Для него необходима dll-библиотека. Поэтому, если приложение будет устанавливаться и на других компьютерах, его надо распространять вместе с dll-библиотекой.
Замечание. Если для создания нового приложения используется MFC AppWizard, библиотека MFC подключается автоматически.
Программисту не нужно вручную вносить изменения в проектный файл в этом случае.
Рассмотрим, как работает приложение first на уровне исходного текста. Сначала в текст приложения включается файл afxwin.h. В этом файле определены классы, методы, константы и другие структуры для библиотеки классов MFC. Кроме того, включаемый файл afxwin автоматически подключает другой файл - windows.h, необходимый для вызовов функций стандартного программного интерфейса Windows.
Сравним исходный текст приложения first с аналогичным приложением, созданным без использования библиотеки классов MFC. В этом приложении присутствует главная функция приложения WinMain, которая вызывается, когда пользователь или операционная система запускает приложение:
#include <windows.h> int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { // Отображение диалоговой панели. MessageBox(NULL,"First MFC-application","Message",MB_OK); // Завершение работы приложения return 0; }
Если посмотреть на текст программы first, то там нет хорошо знакомой функции WinMain. Не видно также переменных, представляющих параметры этой функции.
В приложениях, основанных на классах MFC, функция WinMain скрыта от программиста в определении класса CWinApp. В каждом приложении определяется главный класс приложения, наследуемый от базового класса CWinApp. Приложение должно иметь только один объект главного класса приложения, наследованного от класса CWinApp.
Класс CWinApp выполняет все действия, которые обычно выполняет функция WinMain, - инициализирует приложение, обрабатывает сообщения и завершает приложение. Для этого класс CWinApp включает виртуальные методы InitApplication, InitInstance, Run и ExitInstance.
Чтобы выполнить инициализацию приложения, функция WinMain вызывает методы InitApplication и InitInstance для объекта главного класса приложения. Метод InitApplication выполняет инициализацию на уровне приложения. Программист может переопределить этот метод в своем приложении.
Метод InitInstance выполняет инициализацию каждой копии приложения. Обычно этот метод создает главное окно приложения. Программист должен обязательно переопределить этот метод в своем приложении. Остальные методы, например Run, можно не переопределять.
Затем функция WinMain начинает обрабатывать цикл сообщений. Для этого вызывается метод Run. Можно переопределить этот метод, чтобы реализовать собственный цикл обработки сообщений.
Когда приложение заканчивает работу и цикл обработки сообщений завершается, вызывается метод ExitInstance. Программист может переопределить этот метод, чтобы выполнить какие-либо действия перед завершением приложения.
В случае приложения first главный класс приложения CFirstApp наследуется от базового класса CWinApp. При этом базовый класс указан как public. Это означает, что в программе доступны все элементы базового класса CWinApp, объявленные как public. Можно вызывать методы класса CWinApp для объектов класса CFirstApp и обращаться к элементам данных класса CWinApp.
В объявлении класса CFirstApp объявлен виртуальный метод InitInstance. Этот метод переопределяется в приложении. Изначально метод InitInstance определен в классе CWinApp. Он отвечает за инициализацию приложения. Он вызывается каждый раз, когда пользователь запускает приложение. Если пользователь запустит приложение несколько раз, то метод InitInstance будет вызываться каждый раз.
Метод InitInstance обязательно должен быть переопределен в главном классе приложения. Остальные виртуальные методы можно оставить без изменения.
Сразу после объявления главного класса приложения создается объект theApp этого класса. Объект главного класса приложения должен быть определен как глобальный. В этом случае он будет создан сразу при запуске приложения и сможет управлять всей работой приложения. После создания глобального объекта вызывается функция WinMain, определенная в классе CWinApp. Она выполняет свои обычные функции - регистрирует классы окон, создает окно и т.д.
Нельзя создавать два или более объекта класса, наследованного от базового класса CWinApp.
Каждое приложение должно иметь один и только один объект главного класса приложения.
Метод InitInstance главного класса приложения CFirstApp служит для инициализации. Он вызывается автоматически всякий раз, когда запускается очередная копия приложения. В приложении first метод InitInstance используется для вывода на экран диалоговой панели при помощи функции AfxMessageBox, определенной в MFC.
Вместо функции AfxMessageBox можно воспользоваться функцией MessageBox программного интерфейса Windows. Когда из приложения, созданного с использованием классов MFC, вызываются функции программного интерфейса Windows, необходимо указывать перед именем функции символы ::. Например, вызов функции MessageBox будет выглядеть следующим образом:
::MessageBox(NULL,"First MFC-application","Message",MB_OK);
В конце метода InitInstance вызывается оператор return и возвращается значение FALSE. Приложение сразу же завершается. Если метод InitInsatnce вернет значение TRUE, приложение продолжит свою работу и приступит к обработке очереди сообщений.
Предыдущие два рассматриваемых приложения, фактически никак не могли взаимодействовать с пользователем. Они не имели ни меню, ни панели управления. И, самое главное, они не содержали обработчиков сообщений.
Рассмотрим теперь приложение, которое имеет меню и содержит обработчики сообщений, передаваемых приложению, когда пользователь открывает меню и выбирает из него строки. Пусть меню приложения состоит из одного пункта Test. Можно выбрать одну из следующих команд - Beep или Exit.
Файл ресурсов, в который включается описание меню, можно построить либо непосредственным созданием нового файла ресурсов, либо при помощи средств AppWizard. В любом случае при создании меню нужно определить название меню или строки меню. Каждый элемент меню должен иметь уникальный идентификатор, однозначно его определяющий:
Файл resource.h #define IDR_MENU 101 #define ID_TEST_BEEP 40001 #define ID_TEST_EXIT 40002 Файл resource.rc #include "resource.h" IDR_MENU MENU DISCARDABLE BEGIN POPUP "Test" BEGIN MENUITEM "Beep", ID_TEST_BEEP MENUITEM SEPARATOR MENUITEM "Exit", ID_TEST_EXIT END END
Файлы, в которых находятся определение классов приложения и главного окна, представлены ниже:
Файл menu.h #include <afxwin.h> class CMenuApp: public CWinApp { public: virtual BOOL InitInstance(); }; Файл menu.cpp #include <afxwin.h> #include "menu.h" #include "menum.h" BOOL CMenuApp::InitInstance() { m_pMainWnd= new CMainWindow(); m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMenuApp theApp; Файл menum.h #include <afxwin.h> class CMainWindow : public CFrameWnd { public: CMainWindow(); afx_msg void TestBeep(); afx_msg void TestExit(); // макрокоманда необходима, так как класс обрабатывает сообщения DECLARE_MESSAGE_MAP() }; Файл menum.cpp #include <afxwin.h> #include "menum.h" #include "resource.h" // Таблица сообщений класса BEGIN_MESSAGE_MAP(CMainWindow,CFrameWnd) ON_COMMAND(ID_TEST_BEEP,TestBeep) ON_COMMAND(ID_TEST_EXIT,TestExit) END_MESSAGE_MAP() CMainWindow::CMainWindow() { Create(NULL,"Hello",WS_OVERLAPPEDWINDOW,rectDefault, NULL,MAKEINTRESOURCE(IDR_MENU)); } void CMainWindow::TestBeep()// Метод TestBeep - обрабатывает команду меню { MessageBeep(0); } void CMainWindow::TestExit()// Метод TestExit - обрабатывает команду меню { DestroyWindow(); }
Приложение first не имело главного окна. Для вывода сообщения на экран использовалась функция AfxMessageBox, которая очень похожа на функцию MessageBox программного интерфейса Windows.
Рассмотрим другое приложение- приложение start, оно несколько сложнее предыдущего. При запуске оно отображает на экране компьютера обычное окно, имеющее заголовок, системное меню и кнопки управления.
Точно так же, как и в приложении first, во втором приложении используется класс CWinApp в качестве главного класса приложения. Для управления окном приложения создается еще один класс, наследуемый от базового класса CFrameWnd, входящего в библиотеку MFC.
Файл start.h #include <afxwin.h> class CStartApp: public CWinApp { public: virtual BOOL InitInstance(); }; Файл startm.h #include <afxwin.h> // Класс CMainWindow - представляет главное окно приложения. class CMainWindow : public CFrameWnd { public: CMainWindow(); }; Файл start.cpp #include <afxwin.h> #include "start.h" #include "startm.h" BOOL CStartApp::InitInstance() { // Создание объекта класса CMainWindow m_pMainWnd= new CMainWindow(); // Отображение окна на экране. // Параметр m_nCmdShow определяет режим отображения окна. m_pMainWnd->ShowWindow(m_nCmdShow); // Обновление содержимого окна. m_pMainWnd->UpdateWindow(); return TRUE; } CStartApp theApp; Файл startm.cpp #include <afxwin.h> #include "startm.h" // Конструктор класса CMainWindow CMainWindow::CMainWindow() { // Создание окна приложения Create(NULL,"Hello"); }
Приложение start очень простое - оно состоит из одного главного окна и не содержит ни меню, ни каких-либо других органов управления. И тем не менее главное окно приложения обладает всеми возможностями Windows-окон. Оно имеет заголовок, системное меню и кнопки управления. Можно изменить размер этого окна, увеличить его на весь экран и уменьшить до размера пиктограммы.
В исходных текстах определяется главный класс CStartApp приложения, который наследуется от базового класса CWinApp.
При этом базовый класс указан как public. Можно вызывать методы класса CWinApp для объектов класса CStartApp и обращаться к элементам данных класса CWinApp.
В определении класса CStartApp объявлен виртуальный метод InitInstance. Он переопределяется в файле реализации класса. После объявления главного класса приложения и переопределения функции InitInstance Метод InitInstance главного класса приложения CStartApp служит для инициализации. Он вызывается автоматически каждый раз, когда запускается очередная копия приложения.
Второй класс, класс CMainWindow, наследуется от базового класса CFrameWnd как public и представляет главное окно приложения. В классе главного окна определяется только конструктор.
В данном приложении метод InitInstance используется для отображения на экране окна приложения. Для этого создается объект класса CMainWindow и записывается указатель на этот объект в элемент данных m_pMainWnd класса CWinThread (этот класс является базовым для класса CWinApp). Таким образом, объект приложения и объект окна приложения связываются вместе.
Для создания объекта класса CMainWindow используется оператор new. Он создает объект указанного класса, отводит память и возвращает указатель на него. При создании нового объекта класса окна оператором new для автоматически вызывается конструктор.
Само окно появится на экране только после того, как будет вызван метод ShowWindow. В качестве параметра методу ShowWindow передается значение m_nCmdShow. Переменная m_nCmdShow является элементом класса CWinApp. Его назначение соответствует параметру функции WinMain, который определяет, как должно отображаться главное окно приложения сразу после его запуска.
После появления окна на экране ему передается сообщение WM_PAINT при помощи вызова метода UpdateWindow. По этому сообщению приложение должно обновить содержимое окна.
В конце метода InitInstance вызывается оператор return и возвращается значение TRUE, означающее, что инициализация приложения завершилась успешно и можно приступать к обработке очереди сообщений (eсли метод InitInstance вернет значение FALSE, приложение немедленно завершится; эта возможность использовалась в приложении first).
Для создания окна приложения создается объект класса CMainWindow. Такой объект - это не окно, которое пользователь видит на экране компьютера. Этот объект является внутренним представлением окна. Для создания окна предназначается метод Create, определенный в классе CFrameWnd. Он создает окно и связывает его с объектом C++, в случае приложения start - с объектом класса CMainWindow. Метод Create вызывается в конструкторе класса CMainWindow.
Приведем исходный код динамически подключаемой библиотеки, которая называется MyDLL и содержит одну функцию MyFunction, которая просто выводит сообщение.
Сначала в заголовочном файле определяется макроконтстанта EXPORT. Использование этого ключевого слова при определении некоторой функции динамически подключаемой билиотеке позволяет сообщить компоновщику, что эта функция доступна для использования другими программами, в результате чего он заносит ее в библилтеку импорта. Кроме этого, такая функция, точно так же, как и оконная процедура, должна определяться с помощью константы CALLBACK:
MyDLL.h #define EXPORT extern “C” __declspec (dllexport) EXPORT int CALLBACK MyFunction(char *str);
Файл библиотеки также несколько отличается от обычных файлов на языке C для Windows. В нем вместо функции WinMain имеется функция DllMain. Эта функция используется для выполнения инициализации, о чем будет рассказано позже. Для того, чтобы библиотека осталась после ее загрузки в памяти, и можно было вызывать ее функции, необходимо, чтобы ее возвращаемым значением было TRUE:
MyDLL.c #include <windows.h> #include “MyDLL.h”
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) { return TRUE; } EXPORT int CALLBACK MyFunction(char *str) { MessageBox(NULL,str,”Function from DLL”,MB_OK); return 1; }
После трансляции и компоновки этих файлов появлятся два файла – MyDLL.dll (сама динамически подключаемая библиотека) и MyDLL.lib (ее библиотека импорта).
Пример неявного поключения DLL приложением
Приведем теперь исходный код простого приложения, которое использует функцию MyFunction из библиотеки MyDLL.dll:
#include <windows.h> #include “MyDLL.h”
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { int iCode=MyFunction(“Hello”); return 0; }
Эта программа выглядит как обычная программ для Windows, чем она в сущности и является. Тем не менее, следует обратить внимание, что в исходный ее текст помимо вызова функции MyFunction из DLL-библиотеки включен и заголовочный файл этой библиотеки MyDLL.h.
Также необходимо на этапе компоновки приложения подключить к нему библиотеку импорта MyDLL.lib (процесс неявного подключения DLL к исполняемому модулю).
Чрезвычайно важно понимать, что сам код функции MyFunction не включается в файл MyApp.exe. Вместо этого там просто имеется ссылка на файл MyDLL.dll и ссылка на функцию MyFunction, которая находится в этом файле. Файл MyApp.exe требует запуска файла MyDLL.dll.
Заголовочный файл MyDLL.h включен в файл с исходным текстом программы MyApp.c точно так же, как туда включен файл windows.h. Включение библиотеки импорта MyDLL.lib для компоновки аналогично включению туда всех библиотек импорта Windows. Когда програма MyApp.exe работает, она подключается к библиотеке MyDLL.dll точно так же, как ко всем стандартным динамически подключаемым библиотекам Windows.
Пример динамической загрузки DLL приложением
Приведем теперь полностью исходный код простого приложения, которое использует функцию MyFunction из библиотеки MyDLL.dll, используя динамическую загрузку библиотеки:
#include <windows.h> typedef int (WINAPI *PFN_MyFunction)(char *);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HINSTANCE hMyDll; if((hMyDll=LoadLibrary(“MyDLL”))==NULL) return 1;
PFN_MyFunction pfnMyFunction; pfnMyFunction=(PFN_MyFunction)GetProcAddress(hMyDll,”MyFunction”); int iCode=(*pfnMyFunction)(“Hello”);
FreeLibrary(hMyDll); return 0; }
Обычно создание панели управления и разрешение перемещения панели управления производят при обработке сообщения WM_CREATE для главного окна приложения, например:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { // вызов метода базового класса для корректного создания окна if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1) return -1;
// разрешить присоединение панелей ко всем сторонам окна EnableDocking(CBRS_ALIGN_ANY);
// создание панели управления if (!m_wndToolBar.Create(this) !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) return -1;
// установить характеристики панели управления m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
// разрешить присоединить панель к любой строке родительского окна m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
// присоединить панель управления к родительскому окну DockControlBar(&m_wndToolBar); // создание панели состояния (процесс создания рассматривается ниже) ……… return 0; }
Сообщения от панели управления (от ее кнопок) приходят окну приложения - родительскому окну, в котором размещена эта панель. Поэтому следует добавить в таблицу класса окна соответствующие макрокоманды для получения сообщений от кнопок и включить в класс окна методы для обработки этих сообщений.
Обычно для создания панели управления вызывается метод Create класса CToolBar во время создания окна приложения. В принципе, панель управления можно создать и позже, когда окно уже отображено на экране. Однако, нужно отметить, что в этом случае панель управления возникает не сразу - чтобы панель появилась, необходимо еще изменить размер окна. Оказывается , метод Create класса CToolBar устанавливает нулевой размер окна панели управления. Настоящий размер панели выбирается позже, в зависимости от ее характеристик, а также размеров и характеристик родительского окна.
Чтобы установить правильные размеры и расположение панели управления, следует вызвать метод RecalcLayout. Он входит в класс CFrameWnd и вызывается автоматически, если используются методы CFrameWnd::ShowControlBar, CFrameWnd::FloatControlBar, CMDIChildWnd::Create, а также некоторые другие.
Приведем фрагменты кода, в которых демонстрируется использование стандартных диалоговых панелей выбора файла и процедуры чтения и записи в файл.
Открытие файла и чтение из него
CString m_Text; …… // создание стандартной панели выбора файла Open CFileDialog DlgOpen(TRUE,(LPCSTR)"txt",NULL, OFN_HIDEREADONLY,(LPCSTR)" Text Files (*.txt) |*.txt");
// отображение стандартной панели выбора файла Open if(DlgOpen.DoModal()==IDOK) { // создание объекта и открытие файла для чтения CStdioFile File(DlgOpen.GetPathName(),CFile::modeRead|CFile::typeBinary);
// чтение из файла строки CString& ref=m_Text; File.ReadString(ref); // передается ссылка на строку m_Text }
Открытие файла и запись из него
CString m_Text; …… // создание стандартной панели выбора файла SaveAs CFileDialog DlgSaveAs(FALSE,(LPCSTR)"txt",NULL, OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, (LPCSTR)" Text Files (*.txt) |*.txt");
// отображение стандартной панели выбора файла SaveAs if(DlgSaveAs.DoModal()==IDOK) { // создание объекта и открытие файла для записи CStdioFile File(DlgSaveAs.GetPathName(), CFile::modeCreate|CFile::modeWrite|CFile::typeBinary);
// запись в файл строки File.WriteString((LPCTSTR)m_Text); }
Процессу выделяется свое "частное" 4-гигабайтное виртуальное адресное пространство (вообразите, что у компьютера оперативная память размером в сотни гигабайт и что каждый процесс получает свои 4 Гб). Программа может обращаться к любому байту в этом адресном пространстве, используя единственный 32-битный линейный адрес. При этом в адресном пространстве каждого процесса содержится множество самых разных элементов, в том числе:
образ ЕХЕ-файла программы;
все несистемные DLL, загруженные Вашей программой (включая DLL-модули MFC),
глобальные данные программы (доступные как по чтению и записи, так и только по чтению);
стек программы;
динамически выделяемая память, в том числе кучи Windows и библиотеки С периода выполнения;
файлы, спроецированные в память;
блоки памяти, совместно используемые несколькими процессами;
память, локальная для данного выполняемого потока;
особые системные блоки памяти, в том числе таблицы виртуальной памяти;
ядро и DLL-компоненты Windows.
Слова о том, что адресное пространство процесса — его частная собственность, слегка не верны. Верхние 2 Гб вовсе не являются таковой, и их содержимое — общее для всех процессов. А вот нижние 2 Гб по-настоящему закрыты, в частности, это относится к стеку, кучами глобальной памяти с доступом по чтению/записи. Однако, двух идентичных процессов были отдельные копии кода и данных только для чтения, то какой бы в том был смысл? И действительно, образ ЕХЕ-файла проецируется на адресное пространство каждого процесса по одному и тому же адресу — обычно начиная с 0х40000000. То же относится и к DLL-модулям, если есть возможность их загрузки по одному и тому же адресу для каждого процесса. Закрытая память процесса занимает нижние 2 Гб (от 0 до Ox7FFFFFFF), но младшие 4 Мб (64 Кб под Windows NT) не используются.
Насколько все это надежно? Посторонний процесс практически не имеет возможности перезаписать стек, кучи или глобальную память другого процесса, поскольку эта память, расположенная в нижних 2 Гб виртуального адресного пространства, принадлежит только тому, второму процессу. Пространство, занятое ЕХЕ- и DLL-модулями, помечено как "только для чтения", поэтому в том, что они проецируются на несколько разных процессов, никакой проблемы нет. Однако со старшим гигабайтом адресного пространства дело обстоит хуже, так как сюда отображаются важные данные Windows, доступные по чтению и записи. Сбойная программа вполне может уничтожить расположенные в этой области системные таблицы. (В Windows NT такой проблемы нет, потому что верхние 2 Гб защищены от записи.) Существует также вероятность того, что один процесс испортит содержимое спроецированного в память файла, используемого другим процессом, поскольку файлы проецируются на область, разделяемую всеми процессами. (В Windows NT и это не проблема, так как файлы проецируются на адреса ниже 0x80000000 (в нижние 2 Гб)).
О принципах устройства приложения рассказывалось выше. Теперь рассмотрим, как оно создается с помощью Visual C++. Сначала разберем одно важное понятие - проект. До сих пор приложение рассматривалось, как только как совокупность объектов базовых и производных классов. Но для обеспечения работы приложения требуется нечто большее - наряду с описанием классов необходимо описание ресурсов, связанных с приложением, нужна справочная система и т.п. Термин "проект" как раз и используется, когда имеется в виду такой более общий взгляд на приложение.
В среде Visual C++ можно строить различные типы проектов. Такие проекты после их создания можно компилировать и запускать на исполнение. Фирма Microsoft разработала специальный инструментарий, облегчающий и ускоряющий создание проектов в среде Visual C++. Например, мастер MFC AppWizard (exe) позволяет создать проект Windows-приложения которое имеет однодокументный, многодокументный или диалоговый интерфейс и использует библиотеку MFC.
Создаваемый остов приложения составлен так, что в дальнейшей работе с проектом можно использовать другое инструментальное средство - ClassWizard (мастер классов), предназначенное для создания остовов новых производных классов. Еще одно основное назначение ClassWizard в том, что он создает остовы для переопределяемых методов. Он позволяет показать все сообщения, приходящие классу, и создать остов обработчика любого из этих сообщений. Это только две основные функции ClassWizard. Он не всесилен, но его возможности довольно велики.
Рассмотрим наиболее важные моменты работы Windows и принципы взаимодействия программ с ней.
Интерфейс вызовов функций в Windows
Благодаря данному интерфейсу доступ к системным ресурсам осуществляется через целый рад системных функций. Совокупность таких функций называется прикладным программным интерфейсом, или API (Application Programming Interfase). Для взаимодействия с Windows приложение запрашивает функции API, с помощью которых реализуются все необходимые системные действия, такие как выделение памяти, вывод на экран, создание окон и т.п.
Библиотека MFC инкапсулирует многие функции API. Хотя программам и разрешено обращаться к ним напрямую, все же чаще это будет выполняться через соответствующие функции-члены. Как правило, функции-члены либо аналогичны функциям API, либо непосредственно обращаются к нужной части интерфейса.
Библиотеки динамической загрузки (DLL)
Поскольку API состоит из большого числа функций, может сложиться впечатление, что при компиляции каждой программы, написанной для Windows, к ней подключается код довольно значительного объема. В действительности это не так. Функции API содержатся в библиотеках динамической загрузки (Dynamic Link Libraries, или DLL), которые загружаются в память только в тот момент, когда к ним происходит обращение, т.е. при выполнении программы. Рассмотрим, как осуществляется механизм динамической загрузки.
Динамическая загрузка обеспечивает ряд существенных преимуществ. Во-первых, поскольку практически все программы используют API-функции, то благодаря DLL-библиотекам существенно экономится дисковое пространство, которое в противном случае занималось бы большим количеством повторяющегося кода, содержащегося в каждом из исполняемых файлов. Во-вторых, изменения и улучшения в Windows-приложениях сводятся к обновлению только содержимого DLL-библиотек. Уже существующие тексты программ не требуют перекомпиляции.
Win16 или Win32
В настоящее время широко распространены две версии API. Первая называется Win16 и представляет собой 16-разрядную версию, используемую в Windows 3.1.
Вторая, 32-разрядная версия, называется Win32 и используется в Windows 95 и Windows NT. Win32 является надмножеством для Win16 (т.е. фактически включает в себя этот интерфейс), так как большинство функций имеет то же название и применяется аналогичным образом. Однако, будучи в принципе похожими, оба интерфейса все же отличаются друг от друга. Win32 поддерживает 32-разрядную линейную адресацию, тогда как Win16 работает только с 16-разрядной сегментированной моделью памяти. Это привело к тому, что некоторые функции были модифицированы таким образом, чтобы принимать 32-разрядные аргументы и возвращать 32-разрядные значения. Часть из них пришлось изменить с учетом 32-разрядной архитектуры. Была реализована поддержка потоковой многозадачности, новых элементов интерфейса и прочих нововведений Windows.
Так как Win32 поддерживает полностью 32-разрядную адресацию, то логично, что целые типы данных (intergers) также объявлены 32-разрядными. Это означает, что переменные типа int и unsignerd будут иметь длину 32 бита, а не 16, как в Windows 3.1. Если же необходимо использовать переменную или константу длиной 16 бит, они должны быть объявлены как short. (дальше будет показано, что для этих типов определены независимые typedef-имена.) Следовательно, при переносе программного кода из 16-разрядной среды необходимо убедиться в правильности использования целочисленных элементов, которые автоматически будут расширены до 32 битов, что целочисленных элементов, которые автоматически будут расширены до 32 битов, что может привести к появлению побочных эффектов.
Другим следствием 32-разрядной адресации является то, что указатели больше не нужно объявлять как near и far. Любой указатель может получить доступ к любому участку памяти. В Windows 95 и Windows NT константы near и far объявлены (с помощью директивы #define)пустыми.
Интерфейс GDI
Одним из подмножеств API является GDI (Graphics Device Interfase – интерфейс графического устройства). GDI – это та часть Windows, которая обеспечивает поддержку аппаратно-независимой графики.
Благодаря функциям GDI Windows- приложение может выполняться на самых различных компьютерах.
Многозадачность в Windows
Как известно, все версии Windows поддерживают многозадачность. В Windows 3.1 имеется только один тип многозадачности – основанный на процессах. В более передовых системах, таких как Windows 95 и Windows NT, поддерживается два типа многозадачности: основанный на процессах и основанный на потоках. Давайте рассмотрим их чуть подробнее.
Процесс – это программа, которая выполняется. При многозадачности такого типа две или более программы могут выполняться параллельно. Конечно, они по очереди используют ресурсы центрального процессора и с технической точки зрения, выполняются неодновременно, но благодаря высокой скорости работы компьютера это практически незаметно.
Поток – это отдельная часть исполняемого кода. Название произошло от понятия “направление протекания процесса”. В многозадачности данного типа отдельные потоки внутри одного процесса также могут выполняться одновременно. Все процессы имеют по крайней мере один поток, но в Windows 95 и Windows NT их может быть несколько.
Отсюда можно сделать вывод, что в Windows 95 и Windows NT допускается существование процессов, две или более частей которых выполняются одновременно. Оказывается, такое предположение верно. Следовательно, при работе в этих операционных системах возможно параллельное выполнение, как программ, так и отдельных частей самих программ. Это позволяет писать очень эффективные программы.
Есть и другое существенное различие между многозадачностями Windows 3.1 и Windows 95/NT. В Windows 3.1 используется неприоритетная многозадачность. Это означает, что процесс, выполняющийся в данный момент, получает доступ к ресурсам центрального процессора и удерживает их в течение необходимого ему времени. Таким образом, неправильно выполняющаяся программа может захватить все ресурсы процессора и не давать выполняться другим процессам. В отличие от этого в Windows 95 и Windows NT используется приоритетная многозадачность.
В этом случае каждому активному потоку предоставляется определенный промежуток времени работы процессора. По истечению данного промежутка управление автоматически передается следующему потоку. Это не дает возможность программам полностью захватывать ресурсы процессора. Интуитивно должно быть понятно, что такой способ более предпочтителен.
Взаимодействие программ и Windows
Во многих операционных системах взаимодействие между системой и программой инициализирует программа. Например, в DOS программа запрашивает разрешение на ввод и вывод данных. Говоря другими словами, не- Windows-программы сами вызывают операционную систему. Обратного процесса не происходит. В Windows все совершенно наоборот: именно система вызывает программу. Это осуществляется следующим образом: программа ожидает получения сообщения от Windows. Когда это происходит, то выполняется некоторое действие. После его завершения программа ожидает следующего сообщения.
Windows может посылать программе сообщения множества различных типов. Например, каждый раз при щелчке мышью в окне активной программы посылается соответствующее сообщение. Другой тип сообщений посылается, когда необходимо обновить содержимое активного окна. Сообщения посылаются также при нажатии клавиши, если программа ожидает ввода с клавиатуры. Необходимо запомнить одно: по отношению к программе сообщения появляются случайным образом. Вот почему Windows-программы похожи на программы обработки прерываний: невозможно предсказать, какое сообщение появиться в следующий момент.
Объект событие используется для оповещения процесса или потока о том, сто произошло некоторое событие. Для работы с такими объектами предназначен класс CEvent. Конструктор класса имеет следующий прототип:
CEvent( BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
Значение первого параметра определяет начальное состояние объекта. Если оно равно TRUE, то объект событие установлен (событие произошло), а если FALSE, то объект не установлен или сброшен (событие не произошло).
Второй параметр указывает, каким образом состояние объекта будет изменяться при выполнении события. Если значение параметра равно TRUE (не ноль), то объект может быть сброшен только путем вызова метода ResetEvent класса CEvent. В противном случае объект автоматически сбрасывается после предоставления блокированному потоку доступа к ресурсу.
Третий параметр конструктора указывает на строку, содержащую имя объекта события. Поименованные объекты события становятся системными объектами и могут использоваться другими процессами. Когда два процесса вызывают объекты события с одинаковыми именами, обоим процессам будет предоставлен один и тот же объект - это позволяет синхронизировать процессы. Вместо имени строки можно указать NULL - в этом случае объект события будет локализован внутри одного процесса.
Последний параметр конструктора является указателем на набор атрибутов прав доступа, связанный с объектом события. Если этот параметр равен NULL, то объект событие наследует данный набор у вызвавшего его потока.
Когда объект событие создан, то поток, ожидающий данное событие, должен с помощью этого объекта создать объект типа CSingleLock, для которого затем следует вызвать метод Lock. При этом выполнение данного потока останавливается до тех пор, пока не произойдет ожидаемое событие.
Для сигнализации о том, что событие произошло, предназначена функция SetEvent класса CEvent.
При вызове данной функции первый поток, ожидающий событие, выйдет из остановленного состояния (вызванный им метод Lock завершится и продолжит свое выполнение.
Рассмотрим, как обеспечить синхронизацию потоков на основе семафоров. Прежде всего необходимо создать семафор путем объявления объекта типа CSemaphore. Конструктор этого класса имеет следующий вид:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
Семафор позволяет одному или нескольким потокам получать доступ к объекту. Допустимое число потоков, которым будет разрешен одновременный доступ, указывается во втором параметре. Если это значение равно единице, то семафор будет исключающим, т.е. только один поток или процесс сможет одновременно обращаться к ресурсу.
Семафоры имеют счетчик, указывающий количество задач, указывающий количество задач, которым в настоящее время предоставлен доступ к ресурсу. Если значение счетчика равно нулю, то последующий доступ к ресурсу запрещается до тех пор, пока одна из задач не освободит семафор. Начальное значение счетчика семафора указывается в первом параметре конструктора (это значение не должно быть отрицательным и не должно превышать значение второго параметра конструктора). Если этот параметр равен нулю, то все потоки, ожидающие семафор, будут приостановлены, пока семафор не освободится где-нибудь в программе. Обычно начальное значение задается равным единице, чтобы хотя бы один поток мог получить семафор.
Третий параметр конструктора указывает на строку, содержащую имя объекта семафора. Поименованные семафоры становятся системными объектами и могут использоваться другими процессами. Когда два процесса вызывают семафоры с одинаковыми именами, обоим процессам будет предоставлен один и тот же семафор - это позволяет синхронизировать процессы. Вместо имени строки можно указать NULL - в этом случае семафор будет локализован внутри одного процесса.
Последний параметр конструктора является указателем на набор атрибутов прав доступа, связанный с семафором. Если этот параметр равен NULL, то семафор наследует данный набор у вызвавшего его потока.
Внесем некоторые изменения в пример, рассматриваемый в предыдущем параграфе данной главы.
Добавим в класс CExampleView объект-семафор:
class CExampleView : public CView { protected: // только один поток сможет одновременно обращаться к ресурсу CSemaphore sem; // другие описания класса ..... };
Далее при обработке сообщения от меню создадим два потока. В каждой из функций этих потоков объект-семафор используется для разграничения доступа потоков к ресурсам:
UINT MyThread1(LPVOID pParam); UINT MyThread2(LPVOID pParam); void CExampleView::OnStart() { AfxBeginThread(MyThread1,this); AfxBeginThread(MyThread2,this; } UINT MyThread1(LPVOID pParam) { CExampleView *ptrView=(CExampleView *)pParam; CSingleLock syncObj(&(ptrView->sem)); ....... syncObj.Lock(); // получение семафора действия, связанные с доступом к ресурсу syncObj.Unlock(); // освобождение семафора ....... return 0; } UINT MyThread2(LPVOID pParam) { CExampleView *ptrView=(CExampleView *)pParam; CSingleLock syncObj(&(ptrView->sem)); ....... syncObj.Lock(); // получение семафора действия, связанные с доступом к ресурсу syncObj.Unlock(); // освобождение семафора ....... return 0; }
В каждой из функций потока создается объект типа CsingleLock на базе семафора, а затем вызывается метод Lock. Когда первый поток получает доступ к семафору, другой поток приостанавливается до тех пор, пока первый поток не освободит семафор с помощью функции Unlock. Таким образом, семафор в данном примере предоставляется только одному потоку. Это напоминает работу с исключающим семафором.
С самого начала СОМ разрабатывалась с учетом обеспечения поддержки распределенных сред, т.е. способности клиента создавать объекты на других машинах и вызывать их методы по сети. Эти планы стали реальностью в 1996 году после выпуска распределенной СОМ (Distributed СОМ — DCOM). DCOM позволяет клиенту создавать и использовать объекты как на удаленных системах, так и на локальной. Более того, клиент может даже не осознавать различия между этими двумя случаями. Подобно тому как клиенты СОМ имеют прозрачный доступ к объектам в динамических библиотеках и локальных процессах, DCOM обеспечивает прозрачный доступ к объектам в удаленных процессах. Фактически самое трудное в достижении подобной прозрачности — это обеспечить взаимодействие объектов, исполняющихся в разных процессах независимо от того, выполняются эти процессы на одной машине или нет. В этом смысле, с точки зрения проектирования, DCOM — довольно незначительное расширение оригинальной СОМ.
Возможность запускать удаленные объекты и вызывать их методы — важное достижение, но требуется большее. В частности, нужен способ контроля за тем, кто имеет право создавать объекты на данной машине, и обеспечение безопасного доступа к этим объектам по сети, которая может быть наполнена потенциальными врагами. С этой целью в основу DCOM положен набор сервисов контроля доступа. Приложения (включая программы, созданные до DCOM) могут использовать DCOM и работать вполне безопасно без добавления какого-либо кода, связанного с защитой. С другой стороны, приложения, знающие о новых средствах DCOM контроля доступа, могут задействовать их явно.
Несмотря на отдельные сложные моменты, DCOM вообще проста для понимания. Она добавляет к знакомым основам СОМ всего 3 основных элемента: способ создания удаленного объекта, протокол вызова методов этого объекта и механизмы обеспечения безопасного доступа к нему.
Хотя СОМ с самого начала разрабатывалась с учетом поддержки распределенных систем, ее первоначальная реализация могла работать только на одном компьютере. Объекты СОМ могли быть реализованы в DLL или в отдельном процессе, исполняемом на той же машине, что и их клиент, но не могли располагаться на других машинах в вычислительной сети. Эта ситуация изменилась с появлением распределенной СОМ (Distributed СОМ — DCOM). Теперь объекты СОМ могут предоставлять свои сервисы и клиентам на других машинах.
Для достижения этого DCOM использует вызов удаленной процедуры (RPC). При использовании RPC клиент выполняет нечто похожее на локальный вызов компонента, но на самом деле вызов обрабатывается компонентом на другой машине сети. В DCOM также включена поддержка средств контроля доступа (контроль за тем, какими клиентами может использоваться данный объект), а также возможность указания, на какой машине должен быть создан объект. Сервисы DCOM можно использовать для создания защищенных распределенных приложений СОМ.
Традиционно термин клиент/сервер соотносится с приложением, выполняемым на PC, которое связывается с базой данных, выполняемой на сервере. Это хорошее решение для мелкого и среднего предприятия с количеством пользователей от 5 до 50. Однако, когда количество пользователей достигает 500 и более, поддержка такой системы становится проблематичной (процесс увеличения числа пользователей от одного до сотен тысяч обычно называется масштабируемостью (scalability)). При каждом изменении в программном обеспечении необходимо переустановить новое приложение на каждом клиентном компьютере. Этот процесс требует много времени и средств, учитывая необходимость совместимости всех компонентов.
Что касается приложений Internet и intranet, то они должны основываться на работе с броузерами. Впрочем, если попытаться обслужить тысячи пользователей с помощью простых сценариев CGI, то все закончится обеспечением одновременного доступа к файлам или базе данных.
Сегодня компания Microsoft продвигает технологии OLE и ActiveX (термины, по сути, являющиеся синонимами), которые предусматривают так называемую удаленную автоматизацию. Фактически, удаленная автоматизация (remote automation) позволяет приложению, выполняемому на компьютере РС1, соединиться и создать объект автоматизации на компьютере РС2. Наиболее существенным здесь является не то, что можно заниматься распределенной разработкой без углубленного знания протоколов TCP/IP или NetBEUI, а переход к трехуровневой разработке приложений на основе продуктов Microsoft.
Серьезной проблемой удаленной автоматизации является то, что этот процесс не обладает какой-либо степенью безопасностью, не имеет сервера для управлениями транзакциями или соединениями с базой данных и никогда не предполагался для использования в Internet или intranet.
Позднее Microsoft выпустила спецификацию распределенной модели СОМ (Distributed СОМ — DCOM), улучшающую распределенный аспект объектов модели СОМ, включая поддержку приложений Internet и intranet, но не затрагивающую вопросы безопасности.
Для работы с документом в классе документа обычно определяют элементы (простые переменные, переменные структурного типа, объекты различных классов и др.), отвечающие за хранение данных документа, считываемых и записываемых в файл.
Если в процессе работы с документом выделяется дополнительная память для хранения информации, то в деструкторе класса документа необходимо освободить всю дополнительную память, полученную в процессе работы.
В тех методах класса окна просмотра документа, где содержимое документа (соответствующие элементы класса) изменяется, для получения доступа к документу следует объявить указатель pDoc на текущий документ и получить его при помощи метода GetDocument класса окна просмотра. Изменить содержание документа, на который указывает pDoc. Затем следует установить флаг изменения документа, вызвав метод SetModifiedFlag для объекта, на который указывает pDoc. Далее для mdi-приложения необходимо вызвать метод UpdateAllViews класса документадля перерисовки всех обликов данного документа.
В классе окна просмотра следует переопределить метод OnUpdate для того, чтобы выводить только последние изменения. Иначе этот метод вызывает метод OnDraw для перерисовки всего изображения.
Для восстановления всего изображения в окне просмотра после перекрытия окна необходимо изменить метод OnDraw класса окна просмотра документа, добавив вывод всего изображения:
void CMyView::OnDraw(CDC* pDC) { CDoc* pDoc = GetDocument();
// Вывод содержимого документа, на который указывает pDoc, в окно // просмотра с использованием контекста устройства, указываемого pDC …… }
В классе документа следует определить элементы (простые переменные, переменные структурного типа, объекты различных классов и др.), отвечающие за хранение данных документа, считываемых и записываемых в файл.
Если в процессе работы с документом выделяется дополнительная память для хранения информации, то в деструкторе класса документа необходимо освободить всю дополнительную память, полученную в процессе работы.
В тех методах класса окна просмотра документа, где содержимое документа изменяется, следует выполнить определенные действия:
объявить указатель pDoc на текущий документ и получить его при помощи метода GetDocument класса окна просмотра;
изменить графическое изображение документа;
изменить содержимое документа, на который указывает pDoc;
установить флаг изменения документа, вызвав метод pDoc.SetModifiedFlag(TRUE);
для mdi-приложения вызвать метод pDoc.UpdateAllViews(this) для перерисовки всех обликов документа (кроме текущего), на который указывает pDoc.
Для класса окна просмотра переопределить метод OnUpdate для того, чтобы выводить в этом методе только последние изменения, т.к. метод CView::OnUpdate базового класса просто вызывает метод OnDraw для перерисовки всего изображения.
Для восстановления всего изображения в окне просмотра после перекрытия окна необходимо изменить метод OnDraw класса окна просмотра документа, добавив вывод всего изображения.
После компиляции программы автоматизированного сервера следует произвести его регистрацию в системе, чтобы он мог быть использован клиентами. Именно здесь понадобится третья из предложенных мастером AppWizard функций - DllRegisterServer. Осуществить регистрацию всех автоматизированных объектов, присутствующих на сервере, может любая программа Windows, обратившаяся к этой функции. В системные файлы Windows входит программа regsvr32, которая загрузит DLL модуль сервера и вызовет функцию DllRegisterServer (команда regsvr32.exe SvrDll):
STDAPI DllRegisterServer(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); COleObjectFactory::UpdateRegistryAll(); return S_OK; }
DllRegisterServer обращается к функции COleObjectFactory::UpdateRegistryAll, котора просматривает связанный список имеющихся фабрик класса и сообщает, что каждая из них должна обновить системный реестр для своих объектов.
Достаточно странно, но мастер AppWizard не предоставляет отдельной функции для удаления информации о серверах из реестра. Единственное, что придется сделать, чтобы восполнить пробел, - это ввести дополнительную функцию DllUnregisterServer, практически идентичную DllRegisterServer:
STDAPI DllUnregisterServer(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); COleObjectFactory::UpdateRegistryAll(FALSE); return S_OK; }
Отличается она лишь аргументом, передаваемым функции UpdateRegistryAll, благодаря которому каждому генератору класса будет выставлено требование удалить из реестра информацию относительно своих объектов Automation. Теперь при вызове модуля regsvr32 с параметром /u (команда regsvr32.exe /u SvrDll) произойдет запуск данной функции и информация о сервере в реестре будет уничтожена.
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by DLG.RC // #define IDR_MAINFRAME 128 #define IDM_ABOUTBOX 0x0010 #define IDD_ABOUTBOX 100 #define IDS_ABOUTBOX 101 #define IDD_DLG_DIALOG 102 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 129 #define _APS_NEXT_COMMAND_VALUE 32771 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by SINGLE.RC // #define IDR_MAINFRAME 128 #define IDR_SINGLETYPE 129 #define IDD_ABOUTBOX 100 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_3D_CONTROLS 1 #define _APS_NEXT_RESOURCE_VALUE 130 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 32771 #endif #endif
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by name.rc // #define IDS_NAME 1 #define IDS_NAME_PPG 2 #define IDS_NAME_PPG_CAPTION 200 #define IDD_PROPPAGE_NAME 200 #define IDD_ABOUTBOX_NAME 1 #define IDB_NAME 1 #define IDI_ABOUTDLL 1 #define _APS_NEXT_RESOURCE_VALUE 201 #define _APS_NEXT_CONTROL_VALUE 201 #define _APS_NEXT_SYMED_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 32768
Необходимо создать меню, пиктограмму и строковый ресурс с идентификаторами IDR_OTHERTYPE. Самый простой путь - скопировать и изменить ресурсы с идентификатором IDR_MULTITYPE.
На первом этапе разработки приложения скопированные меню и пиктограмму можно оставить без изменения. В дальнейшем их можно изменить по своему усмотрению.
Строковый ресурс IDR_OTHERTYPE, описывающий документ, желательно изменить сразу. Для графического документа строковый ресурс IDR_MULTITYPE выглядит следующим образом:
IDR_MULTITYPE "\nMulti\nMulti\n\n\nMulti.Document\nMulti Document"
Чтобы текстовый документ имел другое название типа документа, расширения файлов, принятое по умолчанию, нужно изменить строковый ресурс с идентификатором IDR_OTHERTYPE (например, для текствых файлов):
IDR_OTHERTYPE "\nText\nText\nText Files (*.txt)\n.txt\nText.Document\nText Document"
Итак, теперь приложение готово к построению и запуску исполняемого файла. При запуске приложения на экране появится диалоговая панель New, в которой перечислены типы документов, с которыми работает приложение. Такая же диалоговая панель будет выводиться и при создании нового документа при помощи строки New меню File.
Одновременно можно открыть несколько документов различного типа, причем каждый документ может иметь несколько просмотра. Документы каждого типа имеют различные названия, используемые по умолчанию.
Каждый ресурс, представляющий панель управления в редакторе ресурсов Microsoft Visual C++, выступает как единое целое, позволяя одновременно изменять внешний вид кнопок, задавать их идентификаторы и строки описания. В исходном же файле ресурсов приложения ресурс панели управления состоит из трех частей.
Первая часть описывает панель управления, например:
IDR_MAINFRAME TOOLBAR DISCARDABLE 16, 15 BEGIN BUTTON ID_FILE_NEW BUTTON ID_FILE_OPEN BUTTON ID_FILE_SAVE SEPARATOR BUTTON ID_EDIT_CUT BUTTON ID_EDIT_COPY BUTTON ID_EDIT_PASTE SEPARATOR BUTTON ID_FILE_PRINT BUTTON ID_APP_ABOUT END
В данном фрагменте описывается панель управления с идентификатором IDR+MAINFRAME. В блоке BEGIN-END при помощи ключевого слова BUTTON идет описание каждой кнопки панели управления, где задается идентификатор кнопки. Ключевое слово SEPARATOR означает, что между кнопками, разделенными строкой SEPARATOR, увеличивается расстояние. За счет этого достигается эффект разделения кнопок на группы.
В первой части ресурса панели управления отсутствует изображения кнопок. Они располагаются отдельно и представляют вторую часть ресурса. Все кнопки представлены одним bitmap-изображением, имеющим тот же идентификатор, что и соответствующий ресурс TOOLBAR, например:
IDR_MAINFRAME BITMAP MOVEABLE PURE "res\\Toolbar.bmp"
Все изображения кнопок расположены последовательно одна за другой в изображении Toolbar.bmp. Порядок, в котором они расположены, должен соответствовать порядку, в котором кнопки описаны в ресурсе TOOLBAR, и порядку, в котором они будут отображаться на экране. Между отдельными изображениями кнопок должны отсутствовать промежутки, даже если в описании ресурса TOOLBAR присутствуют разделители SEPARATOR.
Для каждой кнопки можно ввести описывающую ее текстовую строку. Эти строковые ресурсы и хранятся в третьей, необязательной части ресурса панели управления.
Соответствие строковых ресурсов кнопкам панели управления достигается за счет присвоения им одинаковых идентификаторов, например:
STRINGTABLE DISCARDABLE BEGIN …….. ID_FILE_NEW "Create a new document\nNew" ….…. END
Надо отметить, что строки описания некоторых кнопок могут отсутствовать или, наоборот, использоваться еще и в других ресурсах, например, в описании ресурсов меню.
Приложение с однооконным интерфейсом, созданное средствами MFC AppWizard, имеет гораздо больше ресурсов, чем приложение, использующее в качестве главного окна приложения диалоговую панель. В нем определены не только диалоговые панели и таблица текстовых строк, но и пиктограмма, меню, панель управления и таблица акселераторов.
Шаблон меню. В ресурсах приложения определен только один шаблон меню, имеющий идентификатор IDR_MAINFRAME.
Когда пользователь выбирает пункт меню, операционная система передает командное сообщение главному окну приложения. Большая часть строк меню IDR_MAINFRAME имеет стандартные идентификаторы, описанные в библиотеке MFC. Некоторые из команд, соответствующих этим идентификаторам, полностью обрабатываются MFC.
Панель управления toolbar. Многие современные приложения, в том числе все приложения, имеющие оконный интерфейс и созданные с использованием средств MFC AppWizard, имеют панель управления. Эта панель располагается, как правило, ниже меню главного окна приложения и содержит ряд кнопок.
Идентификаторы кнопок панели управления соответствуют идентификаторам некоторых строк меню приложения. Поэтому эти строки дублируют соответствующие строки меню.
Образ кнопок панели управления расположен в файле toolbar.bmp, записанном в подкаталоге res каталога проекта. Файл же описания ресурсов приложения single.rc включает себя соответствующий ресурс.
Пиктограмма. В файле ресурсов приложения single определена пиктограмма, представляющая минимизированное приложение. Точно такая же пиктограмма используется всеми приложениями, построенными на основе MFC AppWizard, вне зависимости от типа их интерфейса с пользователем.
Таблица текстовых строк. Одним из самых объемных ресурсов приложения является таблица текстовых строк. В ней определены названия главного окна приложения, строки, отображаемые в панели состояния, и т.д.
Ресурсы приложения содержат несколько блоков, описывающих таблицы текстовых строк. Рассмотрим их. Первый блок текстовых строк включает только одну текстовую строку, имеющую идентификатор IDR_MAINFRAME.
В этой строке закодирована различная информация, относящаяся к типу документов приложения. Обычно для каждого типа документа приложения определена своя строка описания. Формирование этой строки выполняется MFC AppWizard на основании указанной при создании приложения информации.
Второй блок таблицы текстовых строк содержит строки с идентификаторами AFX_IDS_APP_TITLE и AFX_IDS_IDLEMESSAGE. Строка, имеющая идентификатор AFX_IDS_IDLEMESSAGE, отображается в панели состояния, когда приложение находится в ожидании.
Когда пользователь создает объект главного окна приложения, он может указать имя приложения. Если это имя не указано, то в качестве имени приложения используется строка, имеющая идентификатор AFX_IDS_APP_TITLE.
В третьем блоке текстовых строк определены несколько текстовых строк, имеющих стандартные идентификаторы. Эти строки используются для отображения различной информации в панели состояния.
Следующий большой блок текстовых строк содержит краткие описания каждой строки меню приложения. Идентификаторы этих строк соответствуют идентификаторам строк меню, которые они описывают:
Строки, описывающие меню, состоят из двух частей, разделенных символом перевода строки. Первая часть строки отображается в панели состояния, когда пользователь выбирает строки меню. Вторая часть строки содержит краткую подсказку, которая отображается, если поместить указатель курсора мыши на кнопки управляющей панели и подождать несколько секунд. Если такая подсказка не нужна, то вторую часть строки можно не приводить.
Последний большой блок текстовых строк содержит краткие описания каждой строки меню системного приложения. Идентификаторы этих строк соответствуют идентификаторам строк меню, которые они описывают:
Диалоговая панель. В ресурсах приложения определена только одна диалоговая панель с идентификатором IDD_ABOUTBOX. Она содержит краткую информацию о приложении и отображается на экране, когда пользователь выбирает из меню Help строку About:
Таблица акселераторов. Для того, чтобы ускорить доступ к строкам меню приложения, MFC AppWizard добавляет в файл ресурсов таблицу акселераторов.Когда пользователь нажимает комбинацию клавиш, представленную в таблице акселераторов, приложению поступает командное сообщение с соответствующим идентификатором.
Замечание. При внимательном изучении ресурсов приложения можно заметить, что 4 типа ресурса приложения имеют элементы с одинаковыми идентификаторами. Существует меню, строковый ресурс, таблица акселераторов и пиктограмма, которые имеют один и тот же идентификатор IDR_MAINFRAME.
При создании объекта на той же машине, что и клиент, библиотека СОМ этой машины предоставляет выполнение основной работы по запуску соответствующего сервера диспетчеру управления сервисами (Service Control Manager — SCM). Когда же объект создается на удаленной машине, SCM клиентской машины должен в свою очередь делегировать выполнение этой задачи SCM удаленной машины. Для обеспечения стандартной схемы, позволяющей SCM связываться со своим аналогом на удаленной машине, все SCM поддерживают интерфейс IActivation.
Иначе говоря, SCM запрашивает создание объекта у SCM на другой машине через интерфейс IActivation. Очень простой интерфейс IActivation содержит единственную операцию — RemoteActivation, используемую для активизации объектов, создаваемых любыми описанными ранее способами.
Подавляющее большинство классов MFC наследовано от базового класса CObject, лежащего в основе всей иерархии классов этой библиотеки. Методы и элементы данных класса CObject представляют наиболее общие свойства наследованных из него классов MFC.
Класс CObject, а также все классы, наследованные от него, обеспечивают возможность сохранения объектов класса в файлах на диске с их последующим восстановлением.
Для объектов классов, наследованных от базового класса CObject, уже во время работы приложения можно получить разнообразную информацию о классе объекта.
Ряд методов класса CObject предназначен для получения дампа объектов класса во время отладки приложения. Эта особенность класса может ускорить процесс поиска ошибок в приложении.
Каждый объект СОМ реализован внутри некоторого сервера, содержащего код, который реализует методы интерфейсов объекта, а также контролирует данные объекта, пока тот активен. Один сервер может поддерживать (и зачастую поддерживает) более одного объекта некоторого класса и даже поддерживать несколько классов. Рассмотрим три основные типа серверов:
Сервер "в процессе" (in-process): объекты реализуются в динамически подключаемой библиотеке, и, таким образом, исполняются в том же процессе, что и клиент.
Локальный сервер (out-process): объекты реализованы в отдельном процессе, исполняющемся на той же машине, что и клиент.
Удаленный сервер: объекты реализованы в DLL либо в отдельном процессе, которые расположены на удаленном по отношению к клиенту компьютере. Возможность создания таких серверов поддерживает Распределенная СОМ (DCOM).
С точки зрения клиента, объекты, реализованные любой из трех разновидностей серверов, выглядят одинаково; доступ к методам объектов клиент по-прежнему осуществляет через указатели интерфейсов. При необходимости он может проводить различие между разными типами серверов, но это не обязательно. Запуск объекта, получение указателей на его интерфейсы, вызов их методов и освобождение указателей выполняются клиентом одинаково независимо от того, каким сервером реализован объект: "в процессе", локальным или удаленным.
Еще один важный класс, наследуемы от CCmdTarget, называется CDocTemplate. От этого класса наследуется два класса: CSingleDocTemplate и CMultiDocTemplate. Все эти классы предназначены для синхронизации и управления основными объектами , представляющими приложение, - окнами, документами и используемыми ими ресурсами.
// single.cpp : Defines the class behaviors for the application. // #include "stdafx.h" #include "single.h" #include "MainFrm.h" #include "singleDoc.h" #include "singleView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CSingleApp BEGIN_MESSAGE_MAP(CSingleApp, CWinApp) //{{AFX_MSG_MAP(CSingleApp) ON_COMMAND(ID_APP_ABOUT, OnAppAbout) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP // Standard file based document commands ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) // Standard print setup command ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CSingleApp construction CSingleApp::CSingleApp() { // TODO: add construction code here, // Place all significant initialization in InitInstance } ///////////////////////////////////////////////////////////////////////////// // The one and only CSingleApp object CSingleApp theApp; ///////////////////////////////////////////////////////////////////////////// // CSingleApp initialization BOOL CSingleApp::InitInstance() { // Standard initialization // If you are not using these features and wish to reduce the size // of your final executable, you should remove from the following // the specific initialization routines you do not need. #ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL #else Enable3dControlsStatic(); // Call this when linking to MFC statically #endif // Change the registry key under which our settings are stored. // You should modify this string to be something appropriate // such as the name of your company or organization.
SetRegistryKey(_T("Local AppWizard-Generated Applications")); LoadStdProfileSettings(); // Load standard INI file options (including MRU) // Register the application's document templates. Document templates // serve as the connection between documents, frame windows and views. CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CSingleDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CSingleView)); AddDocTemplate(pDocTemplate); // Parse command line for standard shell commands, DDE, file open CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE; // The one and only window has been initialized, so show and update it. m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CAboutDlg dialog used for App About class CAboutDlg : public CDialog { public: CAboutDlg(); // Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: //{{AFX_MSG(CAboutDlg) // No message handlers //}}AFX_MSG DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //{{AFX_DATA_INIT(CAboutDlg) //}}AFX_DATA_INIT } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAboutDlg) //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //{{AFX_MSG_MAP(CAboutDlg) // No message handlers //}}AFX_MSG_MAP END_MESSAGE_MAP() // App command to run the dialog void CSingleApp::OnAppAbout() { CAboutDlg aboutDlg; aboutDlg.DoModal(); } ///////////////////////////////////////////////////////////////////////////// // CSingleApp commands
// single.h : main header file for the SINGLE application // #if !defined(AFX_SINGLE_H__A7203674_E01B_11D1_9525_0080488929D2__INCLUDED_) #define AFX_SINGLE_H__A7203674_E01B_11D1_9525_0080488929D2__INCLUDED_ #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 #ifndef __AFXWIN_H__ #error include 'stdafx.h' before including this file for PCH #endif #include "resource.h" // main symbols ///////////////////////////////////////////////////////////////////////////// // CSingleApp: // See single.cpp for the implementation of this class // class CSingleApp : public CWinApp { public: CSingleApp(); // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CSingleApp) public: virtual BOOL InitInstance(); //}}AFX_VIRTUAL // Implementation //{{AFX_MSG(CSingleApp) afx_msg void OnAppAbout(); // NOTE - the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code ! //}}AFX_MSG DECLARE_MESSAGE_MAP() }; ///////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Developer Studio will insert additional declarations immediately before the previous line. #endif // !defined(AFX_SINGLE_H__A7203674_E01B_11D1_9525_0080488929D2__INCLUDED_)
//Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "#define _AFX_NO_SPLITTER_RESOURCES\r\n" "#define _AFX_NO_OLE_RESOURCES\r\n" "#define _AFX_NO_TRACKER_RESOURCES\r\n" "#define _AFX_NO_PROPERTY_RESOURCES\r\n" "\r\n" "#if !defined(AFX_RESOURCE_DLL) defined(AFX_TARG_ENU)\r\n" "#ifdef _WIN32\r\n" "LANGUAGE 9, 1\r\n" "#pragma code_page(1252)\r\n" "#endif\r\n" "#include ""res\\single.rc2"" // non-Microsoft Visual C++ edited resources\r\n" "#include ""afxres.rc"" // Standard components\r\n" "#include ""afxprint.rc"" // printing/print preview resources\r\n" "#endif" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. #if !defined(AFX_RESOURCE_DLL) defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE 9, 1 #pragma code_page(1252) #endif IDR_MAINFRAME ICON DISCARDABLE "res\\single.ico" IDR_SINGLETYPE ICON DISCARDABLE "res\\singleDoc.ico" #endif ///////////////////////////////////////////////////////////////////////////// // // Bitmap // IDR_MAINFRAME BITMAP MOVEABLE PURE "res\\Toolbar.bmp" ///////////////////////////////////////////////////////////////////////////// // // Toolbar // IDR_MAINFRAME TOOLBAR DISCARDABLE 16, 15 BEGIN BUTTON ID_FILE_NEW BUTTON ID_FILE_OPEN BUTTON ID_FILE_SAVE SEPARATOR BUTTON ID_EDIT_CUT BUTTON ID_EDIT_COPY BUTTON ID_EDIT_PASTE SEPARATOR BUTTON ID_FILE_PRINT BUTTON ID_APP_ABOUT END #if !defined(AFX_RESOURCE_DLL) defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE 9, 1 #pragma code_page(1252) #endif ///////////////////////////////////////////////////////////////////////////// // // Menu // IDR_MAINFRAME MENU PRELOAD DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New\tCtrl+N", ID_FILE_NEW MENUITEM "&Open...\tCtrl+O", ID_FILE_OPEN MENUITEM "&Save\tCtrl+S", ID_FILE_SAVE MENUITEM "Save &As...", ID_FILE_SAVE_AS MENUITEM SEPARATOR MENUITEM "&Print...\tCtrl+P", ID_FILE_PRINT MENUITEM "Print Pre&view", ID_FILE_PRINT_PREVIEW MENUITEM "P&rint Setup...", ID_FILE_PRINT_SETUP MENUITEM SEPARATOR MENUITEM "Recent File", ID_FILE_MRU_FILE1,GRAYED MENUITEM SEPARATOR MENUITEM "E&xit", ID_APP_EXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", ID_EDIT_UNDO MENUITEM SEPARATOR MENUITEM "Cu&t\tCtrl+X", ID_EDIT_CUT MENUITEM "&Copy\tCtrl+C", ID_EDIT_COPY MENUITEM "&Paste\tCtrl+V", ID_EDIT_PASTE END POPUP "&View" BEGIN MENUITEM "&Toolbar", ID_VIEW_TOOLBAR MENUITEM "&Status Bar", ID_VIEW_STATUS_BAR END POPUP "&Help" BEGIN MENUITEM "&About single...", ID_APP_ABOUT END END ///////////////////////////////////////////////////////////////////////////// // // Accelerator // IDR_MAINFRAME ACCELERATORS PRELOAD MOVEABLE PURE BEGIN "N", ID_FILE_NEW, VIRTKEY,CONTROL "O", ID_FILE_OPEN, VIRTKEY,CONTROL "S", ID_FILE_SAVE, VIRTKEY,CONTROL "P", ID_FILE_PRINT, VIRTKEY,CONTROL "Z", ID_EDIT_UNDO, VIRTKEY,CONTROL "X", ID_EDIT_CUT, VIRTKEY,CONTROL "C", ID_EDIT_COPY, VIRTKEY,CONTROL "V", ID_EDIT_PASTE, VIRTKEY,CONTROL VK_BACK, ID_EDIT_UNDO, VIRTKEY,ALT VK_DELETE, ID_EDIT_CUT, VIRTKEY,SHIFT VK_INSERT, ID_EDIT_COPY, VIRTKEY,CONTROL VK_INSERT, ID_EDIT_PASTE, VIRTKEY,SHIFT VK_F6, ID_NEXT_PANE, VIRTKEY VK_F6, ID_PREV_PANE, VIRTKEY,SHIFT END ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 217, 55 CAPTION "About single" STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20 LTEXT "single Version 1.0",IDC_STATIC,40,10,119,8, SS_NOPREFIX LTEXT "Copyright (C) 1998",IDC_STATIC,40,25,119,8 DEFPUSHBUTTON "OK",IDOK,178,7,32,14,WS_GROUP END ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 1,0,0,1 PRODUCTVERSION 1,0,0,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "single MFC Application\0" VALUE "FileVersion", "1, 0, 0, 1\0" VALUE "InternalName", "single\0" VALUE "LegalCopyright", "Copyright (C) 1998\0" VALUE "LegalTrademarks", "\0" VALUE "OriginalFilename","single.EXE\0" VALUE "ProductName", "single Application\0" VALUE "ProductVersion", "1, 0, 0, 1\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_ABOUTBOX, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 210 TOPMARGIN, 7 BOTTOMMARGIN, 48 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE PRELOAD DISCARDABLE BEGIN IDR_MAINFRAME "single\n\nSingle\n\n\nSingle.Document\nSingle Document" END STRINGTABLE PRELOAD DISCARDABLE BEGIN AFX_IDS_APP_TITLE "single" AFX_IDS_IDLEMESSAGE "Ready" END STRINGTABLE DISCARDABLE BEGIN ID_INDICATOR_EXT "EXT" ID_INDICATOR_CAPS "CAP" ID_INDICATOR_NUM "NUM" ID_INDICATOR_SCRL "SCRL" ID_INDICATOR_OVR "OVR" ID_INDICATOR_REC "REC" END STRINGTABLE DISCARDABLE BEGIN ID_FILE_NEW "Create a new document\nNew" ID_FILE_OPEN "Open an existing document\nOpen" ID_FILE_CLOSE "Close the active document\nClose" ID_FILE_SAVE "Save the active document\nSave" ID_FILE_SAVE_AS "Save the active document with a new name\nSave As" ID_FILE_PAGE_SETUP "Change the printing options\nPage Setup" ID_FILE_PRINT_SETUP "Change the printer and printing options\nPrint Setup" ID_FILE_PRINT "Print the active document\nPrint" ID_FILE_PRINT_PREVIEW "Display full pages\nPrint Preview" ID_APP_ABOUT "Display program information, version number and copyright\nAbout" ID_APP_EXIT "Quit the application; prompts to save documents\nExit" ID_FILE_MRU_FILE1 "Open this document" ID_FILE_MRU_FILE2 "Open this document" ID_FILE_MRU_FILE3 "Open this document" ID_FILE_MRU_FILE4 "Open this document" ID_FILE_MRU_FILE5 "Open this document" ID_FILE_MRU_FILE6 "Open this document" ID_FILE_MRU_FILE7 "Open this document" ID_FILE_MRU_FILE8 "Open this document" ID_FILE_MRU_FILE9 "Open this document" ID_FILE_MRU_FILE10 "Open this document" ID_FILE_MRU_FILE11 "Open this document" ID_FILE_MRU_FILE12 "Open this document" ID_FILE_MRU_FILE13 "Open this document" ID_FILE_MRU_FILE14 "Open this document" ID_FILE_MRU_FILE15 "Open this document" ID_FILE_MRU_FILE16 "Open this document" ID_NEXT_PANE "Switch to the next window pane\nNext Pane" ID_PREV_PANE "Switch back to the previous window pane\nPrevious Pane" ID_WINDOW_SPLIT "Split the active window into panes\nSplit" ID_EDIT_CLEAR "Erase the selection\nErase" ID_EDIT_CLEAR_ALL "Erase everything\nErase All" ID_EDIT_COPY "Copy the selection and put it on the Clipboard\nCopy" ID_EDIT_CUT "Cut the selection and put it on the Clipboard\nCut" ID_EDIT_FIND "Find the specified text\nFind" ID_EDIT_PASTE "Insert Clipboard contents\nPaste" ID_EDIT_REPEAT "Repeat the last action\nRepeat" ID_EDIT_REPLACE "Replace specific text with different text\nReplace" ID_EDIT_SELECT_ALL "Select the entire document\nSelect All" ID_EDIT_UNDO "Undo the last action\nUndo" ID_EDIT_REDO "Redo the previously undone action\nRedo" ID_VIEW_TOOLBAR "Show or hide the toolbar\nToggle ToolBar" ID_VIEW_STATUS_BAR "Show or hide the status bar\nToggle StatusBar" END STRINGTABLE DISCARDABLE BEGIN AFX_IDS_SCSIZE "Change the window size" AFX_IDS_SCMOVE "Change the window position" AFX_IDS_SCMINIMIZE "Reduce the window to an icon" AFX_IDS_SCMAXIMIZE "Enlarge the window to full size" AFX_IDS_SCNEXTWINDOW "Switch to the next document window" AFX_IDS_SCPREVWINDOW "Switch to the previous document window" AFX_IDS_SCCLOSE "Close the active window and prompts to save the documents" AFX_IDS_SCRESTORE "Restore the window to normal size" AFX_IDS_SCTASKLIST "Activate Task List" AFX_IDS_PREVIEW_CLOSE "Close print preview mode\nCancel Preview" END #endif #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // #define _AFX_NO_SPLITTER_RESOURCES #define _AFX_NO_OLE_RESOURCES #define _AFX_NO_TRACKER_RESOURCES #define _AFX_NO_PROPERTY_RESOURCES #if !defined(AFX_RESOURCE_DLL) defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE 9, 1 #pragma code_page(1252) #endif #include "res\\single.rc2" // non-Microsoft Visual C++ edited resources #include "afxres.rc" // Standard components #include "afxprint.rc" // printing/print preview resources #endif #endif // not APSTUDIO_INVOKED
// singleDoc.cpp : implementation of the CSingleDoc class // #include "stdafx.h" #include "single.h" #include "singleDoc.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CSingleDoc IMPLEMENT_DYNCREATE(CSingleDoc, CDocument) BEGIN_MESSAGE_MAP(CSingleDoc, CDocument) //{{AFX_MSG_MAP(CSingleDoc) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CSingleDoc construction/destruction CSingleDoc::CSingleDoc() { // TODO: add one-time construction code here } CSingleDoc::~CSingleDoc() { } BOOL CSingleDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; // TODO: add reinitialization code here // (SDI documents will reuse this document) return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CSingleDoc serialization void CSingleDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: add storing code here } else { // TODO: add loading code here } } ///////////////////////////////////////////////////////////////////////////// // CSingleDoc diagnostics #ifdef _DEBUG void CSingleDoc::AssertValid() const { CDocument::AssertValid(); } void CSingleDoc::Dump(CDumpContext& dc) const { CDocument::Dump(dc); } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CSingleDoc commands