Визуальное программирование и MFC

         

Агрегирование


Если включение так легко реализовать, почему бы не использовать эту технику для повторного применения объектов СОМ всегда? Повторное применение объекта всегда может быть реализовано посредством включения — как правило, этого достаточно. Как правило, но не всегда. Точнее, включение — не всегда самое эффективное решение.

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

Эту проблему устраняет агрегирование (aggregation). Оно позволяет внешнему объекту представлять в качестве собственных интерфейсы, на самом деле реализованные внутренним объектом . Когда клиент запрашивает у внешнего объекта указатель на подобный интерфейс, этот объект возвращает указатель на интерфейс внутреннего, агрегированного объекта. (Методы внутреннего объекта добавляются, или агрегируются, к методам внешнего объекта.) Клиент ничего об этом не знает: возвращенный интерфейс обеспечивается для него только одним известным ему объектом, а именно внешним. Агрегирование повышает эффективность, но, как и включение, абсолютно невидимо для клиента.

Однако агрегирование не невидимо для участвующих в нем объектов. В отличие от включения агрегирование требует поддержки со стороны внутреннего объекта. Для этой цели он должен быть особым образом написан; в противном случае объект можно повторно использовать только путем включения. Что же такого особенного в агрегировании, что требует поддержки со стороны внутреннего объекта? Проблемы вытекают из операций, поддержка которых обязательна для всех объектов — операций, определенных в IUnknown. Два основных вопроса при реализации агрегирования — это обеспечение правильного подсчета ссылок и корректной работы Ouerylnterface.




Чтобы понять причины этих проблем, обратимся снова к рисинку. Внешний объект предоставляет интерфейс А и, конечно, поддерживает IUnknown. Внутренний, агрегируемый объект поддерживает интерфейсы В иIUnknown. Так как внешний объект агрегирует внутренний, а не просто включает его, то интерфейс В доступен клиенту внешнего объекта непосредственно.

Допустим, у клиента есть указатель интерфейса В. С точки зрения клиента, этот интерфейс предоставляется ему тем же объектом, что и интерфейс А. Так что у клиента должна быть возможность получить указатель интерфейса А вызовом Ouerylnterface через указатель на интерфейс В. Но откуда внутренний объект знает, что внешний поддерживает интерфейс А? И если клиент вызывает IUnknown: :AddRef через указатель на интерфейс В, то как об этом узнает внешний объект? В конце концов, с точки зрения клиента, существует лишь один объект, так что каждый из этих вызовов должен быть успешным.

Решение обеих проблем очевидно. Любой внутренний объект должен делегировать вызовы методов своего IUnknown методам IUnknown внешнего объекта (агрегирующего его). Следовательно, внутреннему объекту нужно как-то передать указатель на интерфейс IUnknown внешнего. Данный указатель, известный под несколько загадочным названием управляющий IUnknown (controlling unknown), передается как параметр либо CoCreateInstance, либо IClassFactory::CreateInstance при создании агрегируемого объекта. Если соответствующий параметр NULL (самый распространенный случай), то объект знает, что он не агрегируется, и будет обрабатывать все вызовы методов IUnknown самостоятельно. Если не NULL, новый объект будет функционировать только как агрегированный внутренний объект некоторого внешнего объекта — того, что передал ему свой управляющий IUnknown. В последнем случае вызовы методов IUnknown внутреннего объекта делегируются методам IUnknown внешнего объекта, т.е. управляющему IUnknown.

Чтобы все здесь работало правильно, требуется преодолеть и другие сложности. Достаточно сказать, что реализация агрегирования требует определенной работы.И все же ни понимание, ни реализация агрегирования не покажутся сложными, если приложить хоть чуточку усилий. И они окупятся сторицей — ведь агрегирование может существенно повысить производительность при повторном применении объектов.


Архитектура приложения


У всех Windows-приложений фиксированная структура, определяемая функцией WinMain. Структура приложения, построенного из объектов классов библиотеки MFC, является еще более определенной.

Приложение состоит из объекта theApp, функции WinMain, и некоторого количества других объектов. Сердцевина приложения - объект theApp - отвечает за создание всех остальных объектов и обработку очереди сообщений. Объект theApp является глобальным и создается еще до начала работы функции WinMain. Работа функции WinMain заключается в последовательном вызове двух методов объекта theApp: InitInstance и Run. В терминах сообщений можно сказать, WinMain посылает объекту theApp сообщение InitInstance, которое приводит в действие метод InitInstance.

Получив сообщение InitInstance, theApp создает внутренние объекты приложения. Процесс создания выглядит как последовательное порождение одних объектов другими. Набор объектов, порождаемых в начале этой цепочки, определен структурой MFC практически однозначно - это главная рамка, шаблон, документ, облик. Их роли в работе приложения будут обсуждаться позже.

Следующее сообщение, получаемое theApp, - Run - приводит в действие метод Run. Оно как бы говорит объекту: "Начинай работу, начинай процесс обработки сообщений из внешнего мира". Объект theApp циклически выбирает сообщения из очереди и инициирует обработку сообщений объектами приложения.

Некоторые объекты имеют графический образ на экране, с которым может взаимодействовать пользователь. Эти интерфейсные объекты обычно связаны с Windows-окном. Среди них особенно важны главная рамка и облик. Именно им объект прежде всего распределяет сообщения из очереди через механизм Windows-окон и функцию Dispatch.

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

При работе приложения возникают и обычные вызовы одними объектами методов других объектов. В объектно-ориентированной терминологии такие вызовы могут называться сообщениями. В Visual C++ некоторым методам приписан именно этот статус (например, методу OnDraw).



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



ATL-cерверы автоматизации


В этом разделе рассмотрим процессы создания серверов автоматизации с помощью более новой и более компактной библиотеки активных шаблонов (Active Template Library — ATL). Основным преимуществом библиотеки ATL является компактность, но она достигается за счет усложнения процесса разработки. Создание простых приложений остается довольно простым, но разработка сложных представляет большую проблему. Если сервер, к тому же должен обеспечивать развитой пользовательский интерфейс, уметь работать с базами данных, то средств ATL для этого явно недостаточно, поэтому в таких ситуациях рекомендуется создавать MFC-сервера.



Атрибуты контекста устройства


Атрибуты контекста устройства задают общие для всех методов класса CDC параметры графического вывода. При создании контекста устройства они получают значения по умолчанию.

Атрибут контекста устройства

Значение по умолчанию

Тип логической системы координат (mapping mode)

MM_TEXT

Выделенная точка окна (window origin)

(0,0)

Выделенная точка области отображения (viewport origin)

(0,0)

Меры протяженности оконной системы координат (window extents)

(1,1)

Меры протяженности области отображения (viewport extents)

(1,1)

Перо (pen)

BLACK_PEN

Кисть (brush)

WHITE_BRUSH

Шрифт (font)

SYSTEM_FONT

Растровое изображение (bitmap)

Текущая позиция пера (current pen position)

(0,0)

Режим фона (background mode)

OPAQUE

Цвет фона (background color)

белый

Цвет текста (text color)

черный

Режим отображения (drawing mode)

R2_COPYPEN

Режим растяжения (stretching mode)

BLACKONWHITE

Режим заполнения многоугольника (polygon filling mode)

ALTERNATE

Число пробелов между символами (intercharacter spacing)

0

Выделенная точка кисти (brush origin)

(0,0) в экран. координатах

Область усечения (clipping region)

 



Автоматизация


Электронные таблицы, текстовые процессоры и другие программы предоставляют все виды полезных возможностей. Почему бы не обеспечить доступ к ним и другому программному обеспечению? Чтобы это стало возможным, приложения должны предоставлять свои сервисы не только человеку, но и программам — они должны быть программируемыми. Обеспечение программируемости и является целью Автоматизации (Automation, первоначально называвшейся OLE-автоматизацией).

Приложение можно сделать программируемым, обеспечив доступ к его сервисам через обычный СОМ-интерфейс. Однако так поступают редко. Вместо этого доступ к сервисам приложений осуществляется через диспинтерфейсы (dispinterface). Они очень похожи на интерфейсы, (у него есть методы, клиенты осуществляют к нему доступ через указатель интерфейса и т. д.), но имеют и существенные отличия. В частности, методы диспинтерфейса гораздо проще вызывать клиентам, написанным на простых языках типа Visual Basic. Это очень важно: ведь большинство людей, желающих писать программы, осуществляющие доступ к внутренним сервисам приложений, чаще всего выбирают Visual Basic и аналогичные среды.

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

Однако сегодня Microsoft Excel поддерживает Автоматизацию, а это значит, его внутренние сервисы доступны через диспинтерфейсы, поддерживаемые различными СОМ-объектами, предоставляющими методы, скажем, для вычисления среднего значения, проверки правописания и многие другие. Приложения, надстраиваемые над Excel, более не ограничены применением внутреннего макроязыка этой программы, но напротив, могут быть написаны практически на чем угодно. Ныне Excel — не только инструмент для конечных пользователей, но и набор инструментов для разработчиков приложений.

Программируемый доступ к внутренним сервисам посредством Автоматизации поддерживается и рядом других приложений. Именно эта возможность легкого доступа к мощным средствам существующих приложений делает Автоматизацию одной из наиболее широко используемых технологий на основе СОМ.



Автоматизация как механизм обмена информацией


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

Автоматизация - это механизм обмена информацией между процессами (interprocess communications, IPC) в среде Windows, с помощью которого одна прикладная программа или компонент может управлять другой или автоматизировать (automate) ее. Система OLE-автоматизация отличается от высокопроизводительных программ IPC, предусмотренных в Win32, поскольку она обеспечивает лишь выполнение базовых функций, позволяющих связываться и обмениваться информацией обособленным программным модулям. Задача же разработчика - назначить имена межпрограммных интерфейсов и указать способы их использования.

Традиционно проблемы, связанные с IPC, решались в Windows с помощью динамического обмена данными (DDE). DDE - это метод обмена сообщениями, при котором одна программа (клиент DDE) может запрашивать данные у другой (сервера DDE). Существуют три основных типа транзакций или процедур DDE.

"Холодная" связь, при которой клиент запрашивает данные у сервера.

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

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

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

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

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



В-третьих, процесс автоматизированного взаимодействия между двумя обособленными программами происходит косвенно через пару посредник-заглушка (proxy-stub) компонентной модели объекта, но возможно и непосредственное соединение между клиентом-автоматом и основанным на использовании DLL рабочим (внутренним, in-process) сервером-автоматом. Средства автоматизации, как правило, работают быстрее, чем DDE, а производительность рабочих (внутренних) серверов-автоматов всегда будет превосходить производительность DDE-серверов.



И наконец, протокол DDE никогда не был хорошо документирован, и его конкретные реализации зачастую несовместимы между собой. Поскольку система OLE Automation - это прежде всего платформа, на которой разработчик программы-сервера может строить собственные интерфейсы, а разработчик программы-клиента получать доступ к ним, она не страдает такого рода недостатками.




Библиотека MFC


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

Как правило, структура приложения определяется архитектурой Document-View (документ-облик). Это означает, что приложение состоит из одного или нескольких документов - объектов, классы которых являются производными от класса CDocument (класс "документ"). С каждым из документов связаны один или несколько обликов - объектов классов, производных от CView (класс "облик ") и определяющих облик документа.

Класс CFrameWnd ("окна-рамки") и производные от него определяют окна-рамки на дисплее. Элементы управления, создаваемые при проектировании интерфейса пользователя, принадлежат семейству классов элементов управления. Появляющиеся в процессе работы приложения диалоговые окна - это объекты классов, производных от CDialog.

Классы CView, CFrameWnd, CDialog и все классы элементов управления наследуют свойства и поведение своего базового класса CWnd ("окно"), определяющего по существу Windows-окно. Этот класс в свою очередь является наследником базового ласса CObject ("объект").

Одна из трудностей в понимании принципов устройства MFC-приложения, заключается в том, что объекты, из которых оно строится, наследуют свойства и поведение всех своих предков, поэтому необходимо знать базовые классы.



Библиотека СОМ


В любой системе, поддерживающей СОМ, обязательно имеется некоторая реализация библиотеки СОМ. Эта библиотека содержит функции, предоставляющие базовые сервисы объектам и их клиентам. Но гораздо важнее то, что библиотека предоставляет клиентам способ запуска серверов объектов. Доступ к сервисам библиотеки СОМ осуществляется через вызовы обычных функций, а не методов интерфейсов СОМ-объектов. Обычно имена функций библиотеки СОМ начинаются с "Со" — например, CoCreateInstance.

Поиск серверов

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

СОМ реализована на разных системах, и точный формат системного реестра может быть разным. Microsoft Windows и Microsoft Windows NT используют стандартную системную таблицу — она так и называется: Реестр (Registry). Другие реализации СОМ могут использовать другие схемы, которые, однако, должны включать:

CLSID, выступающий как ключ для поиска записи;

указание типа доступного сервера ("в процессе", локальный или удаленный);

для серверов "в процессе" и локальных (т. е. для выполняемых на той же машине, что и клиент) должно быть задано полное имя DLL или исполняемого файла сервера; для удаленных серверов (доступных через DCOM) — указание, где искать исполняемый файл сервера.

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

Классы и экземпляры

Прежде чем перейти к рассмотрению процесса создания объекта библиотекой СОМ, стоит поразмышлять над тем, что это такое. Создать объект — значит, начать исполнение экземпляра класса данного объекта. По крайней мере в случае первого экземпляра класса это подразумевает запуск сервера данного класса.
Фактически основная задача библиотеки СОМ в том, чтобы запустить именно сервер, а не сам объект.



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

Одной библиотеки СОМ для этого мало. СОМ требует, чтобы объект инициализировал сам себя по указанию клиента — это отдельная операция, выполняемая после того, как объект запущен (см. раздел "Инициализация объектов СОМ"). Таким образом, в чистом виде СОМ требует двухэтапного процесса загрузки и инициализации объекта. (Тем не менее есть способ сразу указать и класс, и данные. Эта технология — моникеры. Моникер в состоянии скрыть от клиента все детали, являясь единым указателем и методов, и данных некоторого экземпляра объекта.)


Библиотеки импортирования


При статическом подключении DLL имя .lib-файла определяется среди прочих параметров редактора связей в командной строке или на вкладке “Link” диалогового окна “Project Settings” среды Developer Studio. Однако .lib-файл, используемый при неявном подключении DLL, — это не обычная статическая библиотека. Такие .lib-файлы называются библиотеками импортирования (import libraries). В них содержится не сам код библиотеки, а только ссылки на все функции, экспортируемые из файла DLL, в котором все и хранится. В результате библиотеки импортирования, как правило, имеют меньший размер, чем DLL-файлы. К способам их создания вернемся позднее. А сейчас рассмотрим другие вопросы, касающиеся неявного подключения динамических библиотек.



ChildFrm.cpp


// ChildFrm.cpp : implementation of the CChildFrame class //

#include "stdafx.h" #include "multi.h"

#include "ChildFrm.h"

#ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif

///////////////////////////////////////////////////////////////////////////// // CChildFrame

IMPLEMENT_DYNCREATE(CChildFrame, CMDIChildWnd)

BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd) //{{AFX_MSG_MAP(CChildFrame) // 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()

///////////////////////////////////////////////////////////////////////////// // CChildFrame construction/destruction

CChildFrame::CChildFrame() { // TODO: add member initialization code here }

CChildFrame::~CChildFrame() { }

BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs

return CMDIChildWnd::PreCreateWindow(cs); }

///////////////////////////////////////////////////////////////////////////// // CChildFrame diagnostics

#ifdef _DEBUG void CChildFrame::AssertValid() const { CMDIChildWnd::AssertValid(); }

void CChildFrame::Dump(CDumpContext& dc) const { CMDIChildWnd::Dump(dc); }

#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////// // CChildFrame message handlers



ChildFrm.h


// ChildFrm.h : interface of the CChildFrame class // /////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_CHILDFRM_H__A720368E_E01B_11D1_9525_0080488929D2__INCLUDED_) #define AFX_CHILDFRM_H__A720368E_E01B_11D1_9525_0080488929D2__INCLUDED_

#if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000

class CChildFrame : public CMDIChildWnd { DECLARE_DYNCREATE(CChildFrame) public: CChildFrame();

// Attributes public:

// Operations public:

// Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CChildFrame) virtual BOOL PreCreateWindow(CREATESTRUCT& cs); //}}AFX_VIRTUAL

// Implementation public: virtual ~CChildFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif

// Generated message map functions protected: //{{AFX_MSG(CChildFrame) // 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_CHILDFRM_H__A720368E_E01B_11D1_9525_0080488929D2__INCLUDED_)



Диалоговая панель - главное окно приложения


Диалоговую панель можно использовать и как главное окно приложения. Для этого также сначала следует создать шаблон панели и сгенерировать при помощи ClassWizard класс, отвечающий за работу панели. Затем можно модифицировать шаблон и класс панели для нужд приложения и вызвать эту панель в переопределении метода InitInstance главного класса приложения, начследованного от CWinApp.

В этом случае файл реализации главного класса приложения будет иметь примерно следующий вид:

#include <afxwin.h> #include файл определения главного класса приложения #include файл определения класса диалога BOOL CApp::InitInstance() { // Создание объекта класса диалога CDlg Dlg; // Указатель на объект класса диалога присваивается переменной // m_pMainWnd класса CWinApp (процесс присоединения объекта // главного окна к объекту-приложению) m_pMainWnd= &Dlg; // Отображение на экране диалоговой панели. int result=Dlg.DoModal(); // Проверка возвращаемого методом DoModal значения if(result==IDOK) { …… } else if(result==IDCANCEL) { …… } // Так как диалоговая панель закрыта, то следует возвратить // значение FALSE, чтобы завершить работу приложения return FALSE; }



Динамическая загрузка и выгрузка DLL


Вместо того, чтобы Windows выполняла динамическое связывание с DLL при первой загрузке приложения в оперативную память, можно связать программу с модулем библиотеки во время выполнения программы (при таком способе в процессе создания приложения не нужно использовать библиотеку импорта). В частности, можно определить, какая из библиотек DLL доступна пользователю, или разрешить пользователю выбрать, какая из библиотек будет загружаться. Таким образом можно использовать разные DLL, в которых реализованы одни и те же функции, выполняющие различные действия. Например, приложение, предназначенное для независимой передачи данных, сможет в ходе выполнения принять решение, загружать ли DLL для протокола TCP/IP или для другого протокола.

Загрузка обычной DLL

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

HINSTANCE hMyDll; …… if((hMyDll=::LoadLibrary(“MyDLL”))==NULL) { /* не удалось загрузить DLL */ } else { /* приложение имеет право пользоваться функциями DLL через hMyDll */ }

Стандартным расширением файла библиотеки Windows считает .dll, если не указать другое расширение. Если в имени файла указан и путь, то только он будет использоваться для поиска файла. В противном случае Windows будет искать файл по той же схеме, что и в случае неявно подключенных DLL, начиная с каталога, из которого загружается exe-файл, и продолжая в соответствии со значением PATH.

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

Если файл обнаружен и библиотека успешно загрузилась, функция ::LoadLibrary возвращает ее дескриптор, который используется для доступа к функциям библиотеки.


Перед тем, как использовать функции библиотеки, необходимо получить их адрес. Для этого сначала следует воспользоваться директивой typedef для определения типа указателя на функцию и определить переменую этого нового типа, например:

// тип PFN_MyFunction будет объявлять указатель на функцию, // принимающую указатель на символьный буфер и выдающую значение типа int typedef int (WINAPI *PFN_MyFunction)(char *); …… PFN_MyFunction pfnMyFunction;

Затем следует получить дескриптор библиотеки, при помощи которого и определить адреса функций, например адрес фунции с именем MyFunction:

hMyDll=::LoadLibrary(“MyDLL”); pfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll,”MyFunction”); …… int iCode=(*pfnMyFunction)(“Hello”);

Адрес функции определяется при помощи функции ::GetProcAddress, ей следует передать имя библиотеки и имя функции. Последнее должно передаваться в том виде, в котором эксаортируется из DLL.

Можно также сослаться на функцию по порядковому номеру, по которому она экспортируется (при этом для создания библиотеки должен использоваться def-файл, об этом будет рассказано далее):

pfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll, MAKEINTRESOURCE(1));

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

::FreeLibrary(hMyDll);



Загрузка MFC-расширений динамических библиотек



При загрузке MFC-расширений для DLL (подробно о которых рассказывается далее) вместо функций LoadLibraryи FreeLibrary используются функции AfxLoadLibrary и AfxFreeLibrary. Последние почти идентичны функциям Win32 API. Они лишь гарантируют дополнительно, что структуры MFC, инициализированные расширением DLL, не были запорчены другими потоками.



Ресурсы DLL



Динамическая загрузка применима и к ресурсам DLL, используемым MFC для загрузки стандартных ресурсов приложения. Для этого сначала необходимо вызвать функцию LoadLibrary и разместить DLL в памяти. Затем с помощью функции AfxSetResourceHandle нужно подготовить окно программы к приему ресурсов из вновь загруженной библиотеки.В противном случае ресурсы будут загружаться из файлов, подключенных к выполняемому файлу процесса. Такой подход удобен, если нужно использовать различные наборы ресурсов, например для разных языков.



Замечание. С помощью функции LoadLibrary можно также загружать в память исполняемые файлы (не запускать их на выполнение!). Дескриптор выполняемого модуля может затем использоваться при обращении к функциям FindResource и LoadResource для поиска и загрузки ресурсов приложения. Выгружают модули из памяти также при помощи функции FreeLibrary.


Динамические расширения MFC


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

Чтобы обеспечить возможность свободного обмена указателями на объекты MFC между приложением и DLL, нужно создать динамическое расширение MFC. DLL этого типа подключаются к динамическим библиотекам MFC так же, как и любые приложения, использующие динамическое расширение MFC.

Чтобы создать новое динамическое расширение MFC, проще всего, воспользовавшись мастером приложении, присвоить проекту тип MFC AppWizard (dll) и на шаге 1 включить режим “MFC Extension DLL”. В результате новому проекту будут присвоены все необходимые атрибуты динамического расширения MFC. Кроме того, будет создана функция DllMain для DLL, выполняющая ряд специфических операций по инициализации расширения DLL. Следует обратить внимание, что динамические библиотеки данного типа не содержат и не должны содержать объектов, производных от CWinApp.

Инициализация динамических расширений

Чтобы "вписаться" в структуру MFC, динамические расширения MFC требуют дополнительной начальной настройки. Соответствующие операции выполняются функцией DllMain. Рассмотрим пример этой функции, созданный мастером AppWizard.

static AFX_EXTENSION_MODULE MyExtDLL = { NULL, NULL } ; extern "C" int APIENTRY DllMain(HINSTANCE hinstance, DWORD dwReason, LPVOID IpReserved) { if (dwReason == DLL_PROCESS_ATTACH) { TRACED("MYEXT.DLL Initializing!\n") ; // Extension DLL one-time initialization AfxInitExtensionModule(MyExtDLL, hinstance) ;

// Insert this DLL into the resource chain new CDynLinkLibrary(MyExtDLL); } else if (dwReason == DLL_PROCESS_DETACH) { TRACED("MYEXT.DLL Terminating!\n") ; } return 1; // ok }

Самой важной частью этой функции является вызов AfxInitExtensionModule. Это инициализация динамической библиотеки, позволяющая ей корректно работать в составе структуры MFC. Аргументами данной функции являются передаваемый в DllMain дескриптор библиотеки DLL и структура AFX_EXTENSION_MODULE, содержащая информацию о подключаемой к MFC динамической библиотеке.

Нет необходимости инициализировать структуру AFX_EXTENSION_MODULE явно. Однако объявить ее нужно обязательно. Инициализацией же займется конструктор CDynLinkLibrary. В DLL необходимо создать класс CDynLinkLibrary. Его конструктор не только будет инициализировать структуру AFX_EXTENSION_MODULE, но и добавит новую библиотеку в список DLL, с которыми может работать MFC.



Dlg.cpp


// dlg.cpp : Defines the class behaviors for the application. // #include "stdafx.h" #include "dlg.h" #include "dlgDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CDlgApp BEGIN_MESSAGE_MAP(CDlgApp, CWinApp) //{{AFX_MSG_MAP(CDlgApp) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG ON_COMMAND(ID_HELP, CWinApp::OnHelp) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CDlgApp construction CDlgApp::CDlgApp() { // TODO: add construction code here, // Place all significant initialization in InitInstance } ///////////////////////////////////////////////////////////////////////////// // The one and only CDlgApp object CDlgApp theApp; ///////////////////////////////////////////////////////////////////////////// // CDlgApp initialization BOOL CDlgApp::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 CDlgDlg dlg; m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); if (nResponse == IDOK) { // TODO: Place code here to handle when the dialog is // dismissed with OK } else if (nResponse == IDCANCEL) { // TODO: Place code here to handle when the dialog is // dismissed with Cancel } // Since the dialog has been closed, return FALSE so that we exit the // application, rather than start the application's message pump. return FALSE; }



Dlg.h


// dlg.h : main header file for the DLG application #if !defined(AFX_DLG_H__A7203666_E01B_11D1_9525_0080488929D2__INCLUDED_) #define AFX_DLG_H__A7203666_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 ///////////////////////////////////////////////////////////////////////////// // CDlgApp: // See dlg.cpp for the implementation of this class // class CDlgApp : public CWinApp { public: CDlgApp(); // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CDlgApp) public: virtual BOOL InitInstance(); //}}AFX_VIRTUAL // Implementation //{{AFX_MSG(CDlgApp) // 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_DLG_H__A7203666_E01B_11D1_9525_0080488929D2__INCLUDED_)



Dlg.rc


// 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\\dlg.rc2"" // non-Microsoft Visual C++ edited resources\r\n" "#include ""afxres.rc"" // Standard components\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. IDR_MAINFRAME ICON DISCARDABLE "res\\dlg.ico" #if !defined(AFX_RESOURCE_DLL) defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE 9, 1 #pragma code_page(1252) #endif ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 217, 55 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About dlg" FONT 8, "MS Sans Serif" BEGIN ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20 LTEXT "dlg 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 IDD_DLG_DIALOG DIALOGEX 0, 0, 185, 92 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW CAPTION "dlg" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,128,7,50,14 PUSHBUTTON "Cancel",IDCANCEL,128,23,50,14 LTEXT "TODO: Place dialog controls here.",IDC_STATIC,5,34,113,8 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", "dlg MFC Application\0" VALUE "FileVersion", "1, 0, 0, 1\0" VALUE "InternalName", "dlg\0" VALUE "LegalCopyright", "Copyright (C) 1998\0" VALUE "LegalTrademarks", "\0" VALUE "OriginalFilename","dlg.EXE\0" VALUE "ProductName", "dlg 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 IDD_DLG_DIALOG, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 178 TOPMARGIN, 7 BOTTOMMARGIN, 85 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE DISCARDABLE BEGIN IDS_ABOUTBOX "&About dlg..." 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\\dlg.rc2" // non-Microsoft Visual C++ edited resources #include "afxres.rc" // Standard components #endif ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED



DlgDlg.cpp


// dlgDlg.cpp : implementation file // #include "stdafx.h" #include "dlg.h" #include "dlgDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // 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) //}}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() ///////////////////////////////////////////////////////////////////////////// // CDlgDlg dialog CDlgDlg::CDlgDlg(CWnd* pParent /*=NULL*/) : CDialog(CDlgDlg::IDD, pParent) { //{{AFX_DATA_INIT(CDlgDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CDlgDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CDlgDlg) // NOTE: the ClassWizard will add DDX and DDV calls here //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CDlgDlg, CDialog) //{{AFX_MSG_MAP(CDlgDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CDlgDlg message handlers BOOL CDlgDlg::OnInitDialog() { CDialog::OnInitDialog(); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here return TRUE; // return TRUE unless you set the focus to a control } void CDlgDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialog::OnSysCommand(nID, lParam); } } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CDlgDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } } // The system calls this to obtain the cursor to display while the user drags // the minimized window. HCURSOR CDlgDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; }


DlgDlg.h


// dlgDlg.h : header file // #if !defined(AFX_DLGDLG_H__A7203668_E01B_11D1_9525_0080488929D2__INCLUDED_) #define AFX_DLGDLG_H__A7203668_E01B_11D1_9525_0080488929D2__INCLUDED_ #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 ///////////////////////////////////////////////////////////////////////////// // CDlgDlg dialog class CDlgDlg : public CDialog { // Construction public: CDlgDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data //{{AFX_DATA(CDlgDlg) enum { IDD = IDD_DLG_DIALOG }; // NOTE: the ClassWizard will add data members here //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CDlgDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: HICON m_hIcon; // Generated message map functions //{{AFX_MSG(CDlgDlg) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; //{{AFX_INSERT_LOCATION}} // Microsoft Developer Studio will insert additional declarations immediately before the previous line. #endif // !defined(AFX_DLGDLG_H__A7203668_E01B_11D1_9525_0080488929D2__INCLUDED_)



DLL и MFC


Программист не обязан использовать MFC при создании динамических библиотек. Однако использование MFC открывает ряд очень важных возможностей.

Имеется два уровня использования структуры MFC в DLL. Первый из них — это обычная динамическая библиотека на основе MFC, MFC DLL (regular MFC DLL). Она может использовать MFC, но не может передавать указатели на объекты MFC между DLL и приложениями. Второй уровень реализован в динамических расширениях MFC (MFC extensions DLL). Использование этого вида динамических библиотек требует некоторых дополнительных усилий по настройке, но позволяет свободно обмениваться указателями на объекты MFC между DLL и приложением.



DLL-сервер автоматизации


Итак, это будет внутренний сервер Automation. То есть он будет работать в том же адресном пространстве, что и процесс клиента, и, следовательно, выполняться как DLL модуль. Для создания внутреннего сервера средствами Visual C++ следует просто подготовить фрагмент программы для оболочки DLL. Затем для организации с ним взаимодействия добавить описание интерфейса объекта Automation, указав все его параметры и методы.

При помощи мастера MFC AppWizard (dll) следует подготовить проект приложения SvrDll. В случае проекта DLL требуется только один шаг: задать тип создаваемого DLL модуля и указать, необходимо ли подключение механизма автоматизации.

Выберем вариант "Regular DLL using shared MFC DLL" (стандартный DLL модуль с разделяемым DLL модулем - MFC). Стандартный DLL модуль это модуль, с которым может работать люба прикладная программа Windows, а не только созданная на базе MFC. Возможен другой вариант - "Regular DLL with MFC statically linked" (стандартный DLL модуль с постоянной привязкой к MFC), но постоянно привязанные к создаваемому DLL-модулю фрагменты библиотеки MFC значительно увеличат его размер. Выбор первого из названных вариантов (shared MFC DLL) означает, что комплект поставки создаваемого сервера придется дополнять модулем DLL-библиотеки MFC; однако в этом случае размер файла, содержащего сервер, будет сравнительно небольшим. (Остается еще один вариант, который неприемлем для DLL модулей автоматизированного сервера: "MFC-Extension DLL (using shared MFC DLL)" (расширение DLL при использовании разделяемой MFC DLL). Это расширение DLL предназначено для создания DLL модулей с классами, производными от MFC, которые могут применяться в прикладных программах на базе MFC, и только в них.)

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

В результате создания файлов проекта и текста программы имеется обобщенный вариант внутреннего сервера; осталось лишь описать и разработать конкретный объект и его методы и параметры.



Добавление нового метода


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



Добавление нового события


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



Добавление нового свойства


Как и любой объект, OLE-элемент управления имеет свои методы и свойства. Однако не все они видимы в контейнере. Чтобы стать видимым, они должны быть созданы средством ClassWizad при вызове его вкладки OLE Automation. Видимые свойства и методы контейнер использует для связи с элементом. Используя доступ к свойствам элемента, можно организовать передачу данных между контейнером и элементом управления. Вызывая в контейнере методы элемента, можно организовать обработку этих данных. Это означает по существу реализацию OLE Automation для элементов управления.



Добавление различных методов


Чтобы добавить в класс элемента управления метод автоматизации – метод, который может быть вызван в контейнере, – следует вызвать ClassWizard и выбрать вкладку “OLE Automation”. В появившемся окне следует выбрать кнопку “Add Method”. Затем в диалоговом окне в полях “External name” и “Internal name” ввести имя нового метода (внешнее и внутренне имена могут отличаться, внутреннее имя метода используется в классе элемента, при помощи внешнего имени контейнер может вызывать метод). Возвращаемое методом значение типа выбирается в списке “Return type”. После того, как ClassWizard создаст остов метода, программист может добавить в него код.

ClassWizard наряду с созданием остова метода добавляет в объявление класса описание созданного метода, а в схему диспетчеризации класса (в файле реализации класса) добавляет соответствующий макрос. В раздел интерфейса диспетчеризации odl-файла в его подраздел methods ClassWizard добавит строку, связывающую с методом идентификатор диспетчеризации.

Для определения реакции элемента управления на действия пользователя следует добавить в класс обработчик сообщения. Для построения остовов обработчиков сообщений следует вызвать ClassWizard и выбрать вкладку “Message Map”. Выбрав в поле “Class name” класс, а в поле “Message” необходимое сообщение, следует нажать кнопку “Add Function”. ClassWizard не только добавит в класс объявление созданных методов и их остовы, но и в таблицу обрабатываемых сообщений вставит соответствующие строки.

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



Добавление события


Средство ClassWizard может добавлять элементу управления как базовые, так и пользовательские события. Для этого следует вызовать ClassWizard и открыть вкладку “ActiveX Events”, на которой выберать кнопку “Add Event”. В окне списка “External Name” следует ввести внешнее имя события (например, ClickIn). Параллельно в окне “Internal Name” автоматически появится имя метода FireClickIn, вызов которого будет приводить к посылке сообщения контейнеру. Говорят, что метод FireClickIn “зажигает” событие ClickIn. В окне “Parameter List” задаются параметры, передающиеся вместе с событием. Их типы выбираются из списка типов.

ClassWizard помимо создания события и присвоения ему идентификатора DISPID, а также создания “зажигающего” события метода добавит в раздел отображения события (или в схему событий) соответствующую информацию. Параллельно будет сформирован раздел интерфейса диспетчеризации событий в odl-файле, связывающий событие с идентификатором диспетчеризации.



Добавление свойств


В процессе проектирования OCX-объекта можно добавлять два вида свойств: базовые и пользовательские. Несколько базовых свойств (другое название - стандартные свойства) определены и поддерживаются в базовом классе COleControl. Это общие свойства, пригодные для большинства проектируемых OLE-элементов управления: Appearance - внешний вид (например, 3-мерный или плоский); BackColor - цвет фона; BorderStyle - тиль рамки; Caption - заголовок элемента; Enabled - состояние доступен/недоступен; Font - шрифт при печати заголовка и других сообщений, связанных с элементом управления; ForeColor - цвет переднего плана; hWnd - маркер (дескриптор) окна; ReadyState - состояние готовности; Text - текст, другое название свойства Caption.

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

Member Variable - реализация свойства предполагает введение в созданный класс элемента управления переменной, видимой в контейнере. Это простейший способ реализации. Однако чаще применяются три следующих, когда наряду с переменной создаются и другие элементы класса;

Member Variable With Notification - наряду с переменной создается специальная функция уведомления. Она автоматически вызывается в каркасе приложения всякий раз при изменении значения свойства, что позволяет элементу управления должным образом отреагировать на изменение своего свойства - например, организовать свою перерисовку;

Set/Get Methods - предполагает защищенный доступ к переменной класса, задающей свойство. Хотя сама переменная будет не видима в контейнере, здесь будут доступны автоматически построенные методы Get и Set. Первый позволяет прочитать значение, второй - задать новое значение свойства. Кроме защищенности свойства, этот способ удобен тем, что позволяет организовывать дополнительные вычисления, например, в методе Set можно произвести проверку, разрещающую или запрещающую изменение свойств;




Parameterized - параметризированная реализация применяется, когда переменная класса должна быть массивом и свойство задается не единственным скалярным значением, а совокупностью значений. Эта реализация предполагает построение методов Get и Set. Автоматически эти методы создаются с параметром, включающим индексы элемента массива.

Все свойства имеют определенный тип. Так как OLE не является частью языка Visual C++, то типы OLE отличаются от привычных типов C++. Рассмотрим основные типы OLE: BSTR - строка, каждый символ которой занимает 2 байта; I2 - целое длиной 2 байта; I4 - целое длиной 4 байта; UI4 - целое без знака длиной 4 байта; R4 - вещественное с плавающей точкой длиной 4 байта; R8 - вещественное с плавающей точкой длиной 8 байта; BOOL - булевское значение (целое длиной 2 байта); VARIANT - для переменных, тип значений которых может варьироваться; PTR - указатель на объект; OLE_COLOR - тип (UI4), введенный для переменных, задающих цвет.

Тип свойства выбирается из списка “Type” при добавлении нового свойства при помощи средства ClassWizard. В этом списке, как правило, указываются не типы OLE, а совместимые с ними типы, например тип short, который автоматически транслируется в тип I2. Однако в списке “Type” есть и OLE-типы, например, типы BSTR и OLE_COLOR.



Процедура добавления свойства



Для этого необходимо вызвать ClassWizard и выбрать вкладку OLE Autonation, а затем:

В поле “Class name” необходимо выбрать имя класса элемента управления, производного от COleControl. Напомним, что производные классы наследуют от базового класса его данные и методы. Так все OLE-элементы управления могут использовать открытые для них методы базового класса COleControl.

При помощи кнопки “Add Property” вызвать диалоговое окно для добавления свойств.

Для пользовательских свойств в окно комбинированного списка “External Name” ввести его имя, а для базовых - выбрать имя из этого списка.

В группе “Implementation” для пользовательских свойств включить один из переключателей: “Member Variable” или “Get/Set”.


Для базового свойства будет автоматически выбран переключатель “Stock”. Выбор “Member Variable” задает два первых способа реализации свойства. По умолчанию предлагается второй способ с построением функции уведомления. Если отказаться от нее, то реализуется первый способ. Выбор переключателя “Get/Set” задает третий и четвертый способы реализации свойства. Ввод информации в окне “Parameter List” означает выбор параметризированного свойства, и тогда параметры методов Get и Set будут включать индексы элемента массива, задающего свойство.

Из списка “Type” следует выбрать тип свойства. Для базовых свойств тип задается автоматически.

Задав все характеристики, следует подтвердить создание нового свойства, выбрав кнопку “Ok”.



Действия, выполняемые ClassWizard, при добавлении свойства



Для пользовательского свойства, при добавлении которого был включен переключатель “Member Variable”, ClassWizard вставит в файл CNameCtl.h описание переменной класса и заголовок функции уведомления (если не было отказа). Созданная переменная, кстати, будет видима в контейнере, который будет содержать OLE-элемент управления. Остов функции уведомления создается и вставляется в файл CNameCtl.cpp. Тело функции содержит единственный вызов SetModifiedFlag().

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

Для пользовательского свойства, при добавлении которого был включен переключатель “Get/Set methods”, ClassWizard создаст и добавит в h- и cpp-файлы заголовки и остовы этих методов. В процессе добавления свойств можно отказаться от одного из этих методов, и тогда свойство будет предназначаться “только для чтения” или “только для записи”.

Хотя ClassWizard создает остовы методов Get и Set, пользоваться ими без доработки нельзя, так как он не создает переменной класса.


Ее нужно добавить вручную и соответственно откорректировать тела методов. Следует заметить, что сама переменная не будет видима в контейнере - она скрыта от него, доступны лишь методы Get и Set.



Следующая важная часть работы ClassWizard - формирование раздела DISPATCH_MAP (раздела схемы диспетчеризации) в файле NameCtl.cpp. Для каждого пользовательского или базового свойства добавляется соответствующий макрос. Для базовых свойств добавляется один из макросов с префиксом DISP_STOCKPROP_ в соответствии с задаваемым базовым свойством. Для пользовательских свойств добавляемый макрос зависит от способа реализации свойства. Каждому из четырех способов реализации соответствует свой макрос: DISP_PROPERTY(), DISP_PROPERTY_NOTIFY(), DISP_PROPERTY_EX(), DISP_PROPERTY_PARAM(). Параметрами макросов являются имя класса и свойства, тип свойства, и, в зависимости от типа макроса, имена переменной класса и функции уведомления или имена Get и Set методов.

С каждым свойством ClassWizard связывает идентификатор диспетчеризации ID и добавляет информацию о нем в файл Name.odl.



“Постоянные” свойства и их инициализация



В классе CNameCtrl построен остов метода DoDropExchange - аналог метода DoDataExchange класса диалоговых окон. Этот метод вызывается всякий раз, когда OLE-элемент управления создается, читается или записывается в файл на диск. В ее тело для каждого свойства можно добавить вызов метода с префиксом PX (Propertiy eXchange) - аналог DDX-метода.

Свойство, для которого добавлен вызов PX-метода, называется постоянным (persistent). PX-метод позволяет задать значение постоянного свойства по умолчанию. Для каждого типа свойства применяется свой PX-метод.


Документ приложения (класс CDocument)


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



Документы и облики


Центральными объектами в архитектуре приложения являются один или несколько объектов, называемых документами. Они ориентированы на хранение информации и имеют хорошо развитые методы загрузки, сохранения и управления данными. Документы создаются как объекты классов, производных от класса CDocument библиотеки MFC.

Каждый документ сопровождается одним или несколькими объектами, которые называются обликами. Через облики происходит взаимодействие пользователя с документами. Облик является объектом, предназначенным для отображения документа на экране и распознавания команд пользователя по управлению документом. Для изменения документа облики используют методы документа.

Облики создаются как объекты классов, производных от класса CView библиотеки классов MFC. Кроме класса CView, библиотека содержит ряд классов, производных от CView, которые также можно применять для создания своих классов обликов.

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

Каждый облик должен быть сопоставлен некоторому документу. Один документ может иметь несколько обликов, и в таком случае они, как правило, обеспечивают различное представление документа на экране.



Дополнительные возможности панели состояния


Если во время работы приложения выполняется какой-либо длительный процесс, то в панели состояния можно вывести линейный индикатор progress bar, чтобы показать ход данного процесса.

Методика размещения полосы progress bar достаточно проста. В тот момент, когда понадобится вывести полосу progress bar, просто нужно создать ее, указав в качестве родительского окна панель состояния. Координаты линейного индикатора желательно выбрать так, чтобы он отображался на месте одного из индикаторов (например, на месте индикатора с идентификатором ID_INDICATOR_PRBAR). Предварительно можно убрать рамку с этого индикатора и заблокировать его так, чтобы в нем не отображался текст.

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

void CMainFrame::OnItemMenu() { // получаем индекс индикатора ID_INDICATOR_PRBAR int index=m_wndStatusBar.CommandToIndex(ID_INDICATOR_PRBAR);

// устанавливаем характеристики индикатора ID_INDICATOR_PRBAR m_wndStatusBar.SetPaneInfo(index,ID_INDICATOR_PRBAR, SBPS_DISABLED|SBPS_NOBORDERS,150);

// определяем координаты индикатора ID_INDICATOR_PRBAR RECT r; m_wndStatusBar.GetItemRect(index,&r);

// создаем линейный индикатор CProgressCtrl progress; if(!progress.Create(WS_CHILD|WS_VISIBLE,r,&m_wndStatusBar,1)) return;

// устанавливаем границы для линейного индикатора progress.SetRange(0,100);

// устанавливаем шаг приращения для линейного индикатора progress.SetStep(1);

// плавно увеличиваем положение линейного индикатора for(int i=0; i<100; i++) { Sleep(10); // здесь можно выполнять действия

// выполняем шаг приращения линейного индикатора progress.StepIt(); }

// отображаем текст в самом первом индикаторе m_wndStatusBar.SetWindowText("Progress completed"); }



Дополнительные возможности панели управления


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

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

В том месте ресурса панели управления TOOLBAR, где предполагается вставить дополнительный орган управления, следует вставить разделитель SEPARATOR.

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

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

В класс CToolBar входит метод SetButtonInfo. Этот метод позволяет изменить внешний вид панели управления. Используя этот метод, можно изменить идентификатор, изображение, режим работы и размер разделителей кнопок панели управления. Для определения текущих характеристик кнопок панели можно воспользоваться методом GetButtonInfo класса CToolBar.

Когда на панели создается дополнительный элемент управления, то необходимо указать координаты прямоугольной области, которую он будет занимать. Для определения этих координат следует воспользоваться методом GetItemRect класса CToolBar. Вызов этого метода заполняет структуру типа RECT координатами прямоугольной области, занимаемой кнопкой или разделителем, с индексом nIndex.

Рассмотрим пример создания панели управления с дополнительными элементами - полем редактирования и выпадающим списком. Предварительно создадим два новых идентификатора ID_EDIT и ID_COMBO:

#define ID_EDIT 101 #define ID_COMBO 102

Затем необходимо создать новый класс панелей управления на базе класса CToolBar библиотеки MFC:

class CExtendedBar : public CToolBar { public: // дополнительные элементы управления CEdit m_edit; CComboBox m_combo; };


Далее в классе главного окна приложения следует объявить объект этого нового класса:

class CMainFrame : public CMDIFrameWnd { protected: CExtendedBar m_wndToolBar; // расширенная панель управления // другие описания класса .....…. };

Затем создаем панель управления и добавляем в нее дополнительные элементы при обработке сообщения 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;

// увеличим размер первого разделителя (его индекс 3) m_wndToolBar.SetButtonInfo(3,ID_EDIT,TBBS_SEPARATOR,130); // определяем координаты области, занимаемой разделителем CRect rEdit; m_wndToolBar.GetItemRect(3,&rEdit); rEdit.left+=6; rEdit.right-=6; // отступы

// создаем и размещаем однострочный редактор текста if(!(m_wndToolBar.m_edit.Create(WS_CHILD| ES_AUTOHSCROLL|WS_VISIBLE|WS_TABSTOP| WS_BORDER,rEdit,&m_wndToolBar,ID_EDIT))) return -1; // вводим ограничение на количество вводимых символов m_wndToolBar.m_edit.SetLimitText(10);

// увеличим размер второго разделителя (его индекс 7) m_wndToolBar.SetButtonInfo(7,ID_COMBO,TBBS_SEPARATOR,130); // определяем координаты области, занимаемой разделителем CRect rCombo; m_wndToolBar.GetItemRect(7,&rCombo); rCombo.left+=6; rCombo.right-=6; // отступы rCombo.bottom+=60; // для списка пунктов

// создаем и размещаем выпадающий список if(!(m_wndToolBar.m_combo.Create(WS_CHILD| CBS_DROPDOWNLIST|WS_VISIBLE|WS_TABSTOP, rCombo,&m_wndToolBar,ID_COMBO))) return -1; // добавляем строки в список m_wndToolBar.m_combo.AddString("First"); m_wndToolBar.m_combo.AddString("Second"); m_wndToolBar.m_combo.AddString("Third"); // отмечаем в списке первый пункт m_wndToolBar.m_combo.SetCurSel(0);



// установить характеристики панели управления m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);

// разрешить присоединить панель к любой строке родительского окна m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

// присоединить панель управления к родительскому окну DockControlBar(&m_wndToolBar);

// создание панели состояния (процесс создания рассматривается ниже) ……… return 0; }

Теперь добавим в класс главного окна приложения обработку сообщения WM_COMMAND. Эту обработку будет осуществлять метод OnCommand:

BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam) { char str[80]; if(LOWORD(wParam)==ID_COMBO // сообщение от списка &&HIWORD(wParam)==CBN_SELCHANGE) { sprintf(str,"Item's number:%d",m_wndToolBar.m_combo.GetCurSel()); MessageBox(str); } if(LOWORD(wParam)==ID_EDIT // сообщение от поля ввода &&HIWORD(wParam)==EN_MAXTEXT) { sprintf(str,"Limit text:%d",m_wndToolBar.m_edit.GetLimitText()); MessageBox(str); } return CMDIFrameWnd::OnCommand(wParam, lParam); }


Доступ к удаленному объекту


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



Доступность СОМ


СОМ разработана Microsoft, а поэтому первоначально была доступна под Windows и Windows NT. Теперь Microsoft предоставляет поддержку СОМ и для Macintosh. Хотя Microsoft не поддерживает СОМ на других операционных системах, этот пробел заполнен третьими фирмами. Несколько компаний — больших и малых — предоставляют реализации СОМ и основанных на ней технологий для широкого диапазона операционных систем. Программное обеспечение, разработанное с применением СОМ-объектов, будет доступно на многих системах — от рабочих станций с Windows и Windows NT до мэйнфреймов IBM, работающих под управлением MVS. И, как Вы увидите далее, Распределенная СОМ (DCOM) позволяет СОМ-объектам на всех типах систем взаимодействовать друг с другом. Возрастающая центральная роль СОМ в программном обеспечении для Windows и Windows NT в сочетании с повсеместным распространением этих систем, подсказывает, что данный новый подход к созданию программ найдет свое применение во всех областях человеческой деятельности.



Другие классы


В MFC включено несколько классов, обеспечивающих поддержку приложений, работающих с базами данных. Это такие классы, как CDataBase, CRecordSet, CDaoDataBase. CDaoRecordSet, CDaoQueryDef, CDaoTableDef, CDaoWorkSpace, CLongBinary, CFieldExchange и CDaoFieldExchange.

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

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

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

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

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

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

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



Единообразная передача данных и объекты с подключением


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

Стандартный способ обмена информацией в мире СОМ — Единообразная передача данных (Uniform Data Transfer). Как и любая технология ActiveX или OLE, использующие его приложения должны поддерживать определенные интерфейсы СОМ. Методы этих интерфейсов определяют стандартные способы для описания передаваемых данных, для указания их местоположения и собственно для их пересылки. Они даже определяют простой механизм, позволяющий одному приложению уведомить другое о том, что нужные последнему данные стали доступны. Хотя Единообразная передача данных вряд ли является самым восхитительным аспектом СОМ, она играет важную роль в работе СОМ-приложений.

Полезная в определенных ситуациях простая схема, определенная Единообразной передачей данных для уведомления клиента о наличии интересующих его данных, не вполне достаточна. Именно для ликвидации этих недостатков на основе СОМ была разработана технология Объектов с подключением (Connectable Objects). Обеспечивая более общий механизм обратной связи объекта с клиентом, Объекты с подключением позволяют клиенту легко получать уведомления об интересующих его событиях.



EXE-сервер автоматизации


Рассмотрим кратко процесс создания exe-сервера автоматизации без пользовательского интерфейса.

Сначала при помощи средства MFC AppWizard (exe) следует создать проект приложения SvrExe, основанного на диалоговой панели. При создании проекта необходимо включить отметку флажка “Automation” (для поддержки приложением автоматизации). В результате получится простейшее приложение OLE-автоматизации.

Затем необходимо удалить из проекта (и из каталога) файлы, относящиеся к диалоговой панели класса CSvrExeDlg – SvrExeDlg.h, SvrExeDlg.cpp, DlgProxy.h, DlgProxy.cpp. Из ресурсов приложения следует удалить шаблон диалоговой панели с идентификатором IDD_SVREXE_DIALOG.

Далее нужно изменить файл SvrExe.cpp (файл реализации класса приложения-сервера) следующим образом:

#include "stdafx.h" #include "SvrExe.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif BEGIN_MESSAGE_MAP(CSvrExeApp, CWinApp) //{{AFX_MSG_MAP(CSvrExeApp) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG END_MESSAGE_MAP() CSvrExeApp::CSvrExeApp() { } CSvrExeApp theApp; BOOL CSvrExeApp::InitInstance() { // Инициализация библиотек OLE if (!AfxOleInit()) { AfxMessageBox(IDP_OLE_INIT_FAILED); return FALSE; } // Проверка командной строки – проверка на // запуск в качестве сервера автоматизации if (RunEmbedded() RunAutomated()) { // Регистрация всех фабрик OLE-сервера как выполняемых. // Это позволит OLE создавать объекты из других приложений. COleTemplateServer::RegisterAll(); // Приложение продолжает работу, так как запущено в // качестве сервера автоматизации return TRUE; } else { // Регистрация приложения качестве сервера COleObjectFactory::UpdateRegistryAll(); // Приложение запущено без параметров – прекращение работы return FALSE; } }

С помощью ClassWizard добавить в проект приложения SvrExe класс CSimpleMFC, в качестве базового для него указать класс CCmdTarget (класс, поддерживающий автоматизацию). Обязательно в группе “Automation” включить переключатель “Createable by type ID” и в качестве псевдонима ввести SvrExe.SimpleMFC . Созданный мастером ClassWizard класс CSimpleMFC и является С++ -классом COM-объекта SvrExe.SimpleMFC, реализующим его интерфейс IID_ISimpleMFC (он наследуется от интерфейса IDispatch).

При помощи вкладки “Automation” панели ClassWizard и кнопки “Add property…” для COM-объекта добавляются необходимые ему свойства. А с помощь вкладки “Automation” панели ClassWizard и кнопки “Add method…” в интерфейс COM-объекта добавляются метод автоматизации (например, метод afx_msg void Initialize (long initializer);).

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



Файловая система (класс CFile)


Библиотека MFC включает класс для работы с файловой системой компьютера. Он называется CFile и также наследуется от базового класса CObject. Непосредственно от класса CFile наследуется еще несколько классов - CMemFile, CStdioFile, CSocketFile.

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



Форма панели и режимы работы кнопок


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

Возможность или невозможность изменить форму панели управления определяется методом Create класса CToolBar. Флаг CBRS_SIZE_DYNAMIC позволяет изменять форму панели, а флаг CBRS_SIZE_FIXED запрещает это делать. Изменить эти характеристики можно динамически при помощи метода SetBarStyle.

В состав класса CToolBar входит метод SetButtonStyle. Этот метод позволяет установить режим работы кнопок панели управления, сгруппировав несколько кнопок вместе. Через первый параметр передается индекс кнопки или разделителя в панели управления, а второй параметр позволяет установить новый режим работы. Индекс кнопки или разделителя соответствует их порядковому номеру в панели управления. В качестве второго параметра можно указать комбинацию нескольких флагов:

TBBS_BUTTON - стандартная кнопка.

TBBS_SEPARATOR - разделитель.

TBBS_CHECKBOX - переключатель

TBBS_GROUP - с данной кнопки начинается группа кнопок.

TBBS_CHECKGROUP - с данной кнопки начинается группа переключателей.

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

Определить текущий режим кнопки или разделителя можно при помощи метода GetButtonStyle класса CToolBar.

Индекс, или порядковый номер, любой кнопки панели управления можно определить по ее идентификатору. Для этого предназначен метод CommandToIndex класса CToolBar. Обратную задачу - по индексу кнопки возвращает ее идентификатор - выполняет метод GetItemID.



Функция DllMain


Большинство библиотек DLL — просто коллекции практически независимых друг от друга функций, экспортируемых в приложения и используемых в них. Кроме функций, предназначенных для экспортирования, в каждой библиотеке DLL есть функция DllMain. Эта функция предназначена для инициализации и очистки DLL. Она пришла на смену функциям LibMain и WEP, применявшимся в предыдущих версиях Windows. Структура простейшей функции DllMain может выглядеть, например, так:

BOOL WINAPI DllMain (HANDLE hInst,DWORD dwReason, LPVOID IpReserved) { BOOL bAllWentWell=TRUE; switch (dwReason) { case DLL_PROCESS_ATTACH: // Инициализация процесса. break; case DLL_THREAD_ATTACH: // Инициализация потока. break; case DLL_THREAD_DETACH: // Очистка структур потока. break; case DLL_PROCESS_DETACH: // Очистка структур процесса. break; } if(bAllWentWell) return TRUE; else return FALSE; }

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

При первой загрузке библиотеки DLL процессом вызывается функция DllMain с dwReason, равным DLL_PROCESS_ATTACH. Каждый раз при создании процессом нового потока DllMainO вызывается с dwReason, равным DLL_THREAD_ATTACH (кроме первого потока, потому что в этом случае dwReason равен DLL_PROCESS_ATTACH).

По окончании работы процесса с DLL функция DllMain вызывается с параметром dwReason, равным DLL_PROCESS_DETACH. При уничтожении потока (кроме первого) dwReason будет равен DLL_THREAD_DETACH.

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

В состав DLL могут входить ресурсы, не принадлежащие вызывающему эту библиотеку приложению. Если функции DLL работают с ресурсами DLL, было бы, очевидно, полезно сохранить где-нибудь в укромном месте дескриптор hInst и использовать его при загрузке ресурсов из DLL. Указатель IpReserved зарезервирован для внутреннего использования Windows. Следовательно, приложение не должно претендовать на него. Можно лишь проверить его значение. Если библиотека DLL была загружена динамически, оно будет равно NULL. При статической загрузке этот указатель будет ненулевым.

В случае успешного завершения функция DllMain должна возвращать TRUE. В случае возникновения ошибки возвращается FALSE, и дальнейшие действия прекращаются.

Замечание. Если не написать собственной функции DllMain(), компилятор подключит стандартную версию, которая просто возвращает TRUE.



Функция VirtualAlloc: переданная и зарезервированная память


Если программе нужна динамически распределяемая память, то рано или поздно ей придется вызвать функцию VirtualAlloc. Скорее всего вызовете ее не программист, а функции Windows или библиотеки С периода выполнения, выделяющие память из кучи. Зная, как работает VirtuаlAlloc, можно лучше понять функции, которые обращаются к ней.

Сначала разберемся с понятиями зарезервированной (reserved) и переданной (committed памяти. При резервировании памяти выделяется непрерывный диапазон виртуальных адресов. Если, допустим, известно, что программа будет оперировать с одним 5-мегабайтным блоком памяти (блоки памяти называют также регионами (regions)), но весь он сейчас не нужен, тогда следует вызвать VirtualAlloc и в параметре, определяющем тип выделения памяти, указать MEM_RESERVE, а в параметре, задающем размер выделяемой памяти, — 5 Мб. Windows округляет начальный и конечный адреса региона до значений, кратных 64 Кб, и уже не даст процессу повторно зарезервировать память из этого региона. И хотя программист может задавать начальный адрес региона, лучше оставить это занятие самой Windows. Ну вот, собственно, и все. Больше ничего не происходит — не выделяется ни оперативной памяти, ни пространства в файле подкачки.

Когда программе всерьез понадобится эта память, она снова вызовет VirtualAlloc, на этот раз ука, МЕМ_СОММIТ, чтобы передать память из этого региона. Теперь начальный и конечный адреса региона округляются до значений, кратных 4 Кб, и в файле подкачки выделяются соответствующие страницы, а также создается нужная таблица страниц. Блок помечается либо как "только для чтения", либо как "для чтения и записи". Однако оперативная память по-прежнему не выделяется; это произойдет, лишь когда программа попытается что-то записать в этот блок памяти. Если передаваемая память не была ранее зарезервирована, ошибки все равно не возникает. А если память уже была передана, то и в этом случае — никаких проблем. Главное в том, что перед использованием память должна быть передана.

Чтобы вернуть (decommit) переданную память (по сути, вернуть соответствующим страницам статус зарезервированных), применяется функция VirtualFree. Она может также освободить и зарезервированный регион памяти, но для этого ей надо передать базовый адрес, возвращенный VirtualAlloc в момент резервирования памяти.



Галерея компонентов и элементов управления


Сначала рассмотрим способ хранения OCX-объектов - своего рода библиотеку, где можно найти подходящий для определенных целей объект. Такой библиотекой является галерея компонентов и элементов управления (Component and Controls Gallery) - вид визуальной библиотеки, использующей классификацию хранимых в ней программных компонентов, которые можно задействовать в приложениях, разработанных в среде Visual C++. Компоненты хранятся по категориям. Например, OLE-элементы управления хранятся в категории Registered ActiveX Control (OLE-Controls). В качестве компонентов выступают классы (возможно вместе с необходимыми ресурсами), элементы управления ActiveX (OCX), а также сложные компоненты. Компоненты можно включать в создаваемое приложение и использовать по своему усмотрению.

Количество страниц галереи компонентов и набор компонент зависит от версии Visual C++ и постоянно расширяется. При работе с галереей можно создать собственную категорию, переименовать ее, добавлять или перемещать компоненты из одной категории в другую. В галерею компонентов можно включать компоненты, разработанные другими фирмами, а также собственные компоненты, разработанные самим программистом (в простейшем случае, в качестве таких компонент могут выступать классы созданных приложений). Главное, что любой компонент можно вставить в собственное приложение.

На странице Developer Studio Components расположены компоненты, разработанные фирмой Microsoft. Среди них содержатся много компонент, добавляющих к приложению различные возможности практически без дополнительных затрат со стороны программиста. Среди них - компонент, позволяющих приложениям использовать обменный буфер Clipboard, компонент, выполняющий фоновые работы во время бездействия приложения, компонент, содержащий готовые диалоговые панели для ввода пароля и отображения хода различных процессов и многое другое.

Некоторые компоненты, расположенные на странице компонент Microsoft, дублируют возможности приложения, которыми можно управлять в ходе создания приложения средствами MFC AppWizard.
Поэтому, например, если во время начального создания проекта приложения не было указано, что приложение будет работать с сокетами Windows, то вместо кропотливого исправления проекта вручную можно просто добавить в него компонент Windows Sockets.

На странице Registered ActiveX Controls галереи представлены органы управления OCX (OLE Controls), зарегистрированные в реестре Windows. Количество их очень обычно велико, поэтому рассмотрим только некоторые элементы, которые поставляются вместе с Microsoft Visual C++: Grid Control - сетка или таблица, в каждой ячейке которой можно вывести текст или изображение; Microsoft Comm Control - элемент управления, который позволяет обмениваться информацией через последовательный порт; Video Play Control - элемент управления, который позволяет воспроизводить видео, записанное в форматах AVI, MPEG или QuickTime; Anibutton Control - кнопка с рисунком; Microsoft Multimedia Control - набор кнопок для приложений мультимедиа; ASP Arrow (Non Rectangle OLE Control) - кнопки, имеющие нестандартные формы.

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

Компонент можно добавить в галерею четырьмя способами:

импортировать;

автоматически добавить OLE-элемент управления при его регистрации (Автоматическая регистрация происходит только при первом добавлении элемента управления. Если же его удалить, а потом снова зарегистрировать, то, хотя сообщение о регистрации появляются, элемент управления в галерею автоматически не добавляется и его приходится импортировать);

добавить класс в момент его создания средством ClassWizard;

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

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



открыть проект, в который необходимо добавить компонент;

войти в галерею, выбрать нужную категорию и компонент;

при помощи кнопки “Insert” добавить компонент в проект. В зависимости от типа компонента предварительно может появиться окно с дополнительной информацией. Для OLE-элемента управления будет выведено окно, где указаны классы, которые будут добавлены в проект для обеспечения взаимодействия проекта и добавляемого элемента управления

Когда в проект добавляется OLE-элемент управления (OCX-объект), галерея компонентов выполняет следующие действия: проводит регистрацию незарегистрированного элемента управления в базе данных Windows для OLE-элементов управления; добавляет инструментальную кнопку, соответствующую данному элементу, в панель инструментов редактора диалога (так что наряду с кнопками элементов управления Windows, на панели инструментов появится новая кнопка, задающая OLE-элемент управления; добавляет в проект классы, определяющие свойства и поведение OLE-элемента управления.


Визуальное программирование и MFC


Значениями ряда атрибутов контекста устройства являются объекты GDI. Как отмечалось ранее, в вызовах методов, рисующих фигуры на экране, многие параметры не указываются, а берутся из атрибутов контекста устройства. Чтобы эти параметры отличались от установленных в контексте устройства по умолчанию, необходимо:

Сохранить старое значение атрибута.

Установить новое.

Выполнить необходимые действия.

Восстановить старое значение атрибута.

Последовательность этих действий иллюстрируется примером:

void CMyView::OnDraw(CDC* pDC) { CPen Pen; if(Pen.CreatePen(PS_SOLID,2,RGB(0,0,0)) { // сохранение старого и установление нового значения атрибута CPen* pOldPen=pDC->SelectObject(&Pen); // выполнение необходимых действий pDC->MoveTo(....); pDC->LineTo(....); // восстановление старого значения атрибута pDC->SelectObject(pOldPen); } }

Метод SelectObject в качестве результата возвращает указатель на текущее перо и делает текущим перо, указанное в качестве параметра метода.



Введение в Visual C++


В связи с тем, что сегодня уровень сложности программного обеспечения очень высок, разработка приложений Windows с использованием только какого-либо языка программирования (например, языка C) значительно затрудняется. Программист должен затратить массу времени на решение стандартных задач по созданию многооконного интерфейса. Реализация технологии связывания и встраивания объектов - OLE - потребует от программиста еще более сложной работы.

Чтобы облегчить работу программиста практически все современные компиляторы с языка C++ содержат специальные библиотеки классов. Такие библиотеки включают в себя практически весь программный интерфейс Windows и позволяют пользоваться при программировании средствами более высокого уровня, чем обычные вызовы функций. За счет этого значительно упрощается разработка приложений, имеющих сложный интерфейс пользователя, облегчается поддержка технологии OLE и взаимодействие с базами данных.

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

Подобные средства автоматизированного создания приложений включены в компилятор Microsoft Visual C++ и называются MFC AppWizard. Заполнив несколько диалоговых панелей, можно указать характеристики приложения и получить его тексты, снабженные обширными комментариями. MFC AppWizard позволяет создавать однооконные и многооконные приложения, а также приложения, не имеющие главного окна, -вместо него используется диалоговая панель. Можно также включить поддержку технологии OLE, баз данных, справочной системы.

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

Нужно отметить, что MFC AppWizard создает тексты приложений только с использованием библиотеки классов MFC (Microsoft Foundation Class library). Поэтому только изучив язык C++ и библиотеку MFC, можно пользоваться средствами автоматизированной разработки и создавать свои приложения в кратчайшие сроки.



Краткий обзор классов MFC


Библиотека классов MFC содержит большое количество разнообразных классов. Каждый класс, как правило, содержит от нескольких единиц до нескольких десятков различных методов и элементов данных. Для изучения иерархии классов MFC можно воспользоваться документацией или справочной системой среды Microsoft Developer Studio.

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



Простейшие MFC-приложения


Самые простые приложения с использованием библиотеки классов MFC можно создавать в Microsoft Developer Studio без применения автоматизированных средств разработки приложений MFC AppWizard. Необходимо только создать проект типа Win32 Application и включить в его установки поддержку библиотеки MFC.



Диалоговые панели (использование ClassWizard)


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

Библиотека классов MFC содержит класс CDialog, специально предназначенный для управления диалоговыми панелями. Диалоговые панели бывают двух типов - модальные и немодальные.

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

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

И модальные и немодальные диалоговые панели обслуживаются одним (общим) классом CDialog, наследованным от базового класса CWnd.

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

Следующим этапом является создание класса для управления диалоговой панелью. Этот класс наследуется непосредственно от базового класса CDialog и генерируется средством ClassWizard пакета Microsoft Developer Studio.

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

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

Чтобы создать модальную диалоговую панель, сначала необходимо создать объект определенного в приложении класса диалоговой панели, а затем вызвать метод DoModal, определенный в классе CDialog.

Процедура создания немодальной диалоговой панели несколько другая. Для этого используется метод Create класса CDialog. Процесс создания немодальных диалоговых панелей будет рассматриваться позже. Сейчас остановимся на создании модального диалога.



Приложение, основанное на диалоге (использование AppWizard)


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



Обзор классов окон библиотеки MFC


Очень много объектов, рассматриваемых в предыдущих главах, связаны с Windows-окнами. К ним прежде всего относятся облики и диалоги - объекты классов CView и CDialog и их производные. Элементы управления (окна редактирования, списки, кнопки и др.) - тоже связаны с окнами, изображающими их на экране. Существующий практически в каждом приложении объект главное окно-рамка тоже представлен на экране Windows-окном - главным окном приложения. Классы всех этих объектов являются производными от класса CWnd. Он-то и обеспечивает отображение окна на экране и работу с ним.



Архитектура Document-View


Ранее рассматривалось приложение, основанное на диалоге. Такое приложение типично для визуального программирования. Однако для Visual C++ такие приложения не являются основными. В Visual C++ модель визуального программирования получила дальнейшее развитие в виде архитектуры приложения Document-View. Средство AppWizard позволяет создавать приложения, основанные на документах: приложения с однодокументным интерфейсом (SDI - Single Document Interface) и приложения с многодокументным интерфейсом (MDI - Multiple Document Interface). Они и являются основными для Visual C++.

Созданное в Visual C++ приложение функционирует как набор взаимодействующих объектов. Этот набор не случаен и выбирается не только программистом. В Visual C++ эти объекты организованы в систему со вполне четкой архитектурой Document-View.

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

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

Приложение должно строиться так, чтобы, видя один или несколько документов, пользователь сосредоточил внимание на них самих, а не на средствах работы с документами. Архитектура Document-View как раз предоставляет, а в некотором смысле навязывает программисту систему объектов, позволяющих строить приложения, сфокусированные на данных, а точнее - на документах.



Приложение с однодокументным интерфейсом (использование AppWizard)


Рассмотрим проект однооконного приложение single, созданного с использованием средств MFC AppWizard. Пусть при его создании задавались следующие характеристики: приложение с однодокументным интерфейсом (SDI-приложение); возможность печати и предварительного просмотра документа; наличие панели управления (tool bar) и панели состояния (status bar); приложение не поддерживает ни технологию OLE, ни базу данных, ни сетевые технологии.

В состав проекта single входят следующие основные файлы (см. ):

single.h - в этом файле перечислены другие включаемые файлы и описан главный класс приложения CSingleApp.

single.cpp - основной файл приложения. В нем определены методы основного класса приложения CSingleApp.

MainFrm.h - содержит описание класса окна-рамки (frame), который называется CMainFrame. Класс CMainFrame наследуется от базового класса CFrameWnd, определенного в библиотеке классов MFC.

MainFrm.cpp - файл содержит определение методов класса CMainFrame.

singleDoc.h - содержит описание класса документов приложения CSingleDoc.

singleDoc.cpp - включает определение методов класса CSingleDoc.

singleView.h - содержит описание класса окна просмотра приложения CSingleView.

singleView.cpp - включает определение методов класса CSingleView.

single.rc - файл ресурсов. В этом файле описаны все ресурсы приложения. Сами ресурсы приложения могут быть записаны в каталоге Res, расположенном в главном каталоге проекта.

resource.h - файл содержит определения идентификаторов ресурсов приложения.

res\single.ico - пиктограмма приложения.

res\single.rc2 - в этом файле определены ресурсы, которые нельзя редактировать с помощью редактора ресурсов среды Visual C++.

res\toolbar.bmp - файл содержит изображение кнопок панели управления toolbar.

StdAfx.h, StdAfx.cpp - использование этих файлов позволяет ускорить процесс повторного построения проекта.

readme.txt - текстовый файл, содержащий описание проекта. В нем кратко рассмотрен каждый файл, входящий в проект, перечислены классы приложения, а также представлена другая информация.

После построения проекта single и запуска полученного приложения на экране появляется главное окно приложения. Оно имеет меню, панели управления и состояния.

Некоторые из строк меню приложения уже работают. Например, когда выбирается из меню File строка Open, на экране появляется диалоговая панель для выбора файла. Можно выбрать любой файл и открыть его. Однако от этого изменится только заголовок окна приложения - в нем появится название открытого файла. Содержимое файла будет недоступно. Чтобы просматривать и изменять содержимое открытого файла, необходимо добавить специальный код. Решение этой проблемы будет рассматриваться далее.



Приложение с многодокументным интерфейсом (использование AppWizard)


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

Пусть при помощи AppWizard создан проект приложения multi с многооконным интерфейсом (без использования технологии OLE, сетевых технологий, баз данных). После построения проекта и запуска приложения на экране появиться главное окно. Внутри главного окна расположены меню, панель управления и панель состояния.

Сразу после запуска приложения открывается дочернее окно, предназначенное для просмотра документа, которое получает название Multi1. При помощи строк New и Open меню File можно создавать новые дочерние окна. Если одновременно открыто несколько окон, то можно упорядочить расположение этих окон и пиктограмм, представляющих минимизированные окна. Для этого предназначено меню Window.

Опишем основные файлы проекта multi (см. ):

MainFrm.cpp - содержит определение методов класса CMainFrame.

MainFrm.h - содержит описание класса главного окна приложения, который называется CMainFrame. Класс CMainFrame наследуется от базового класса CMDIFrameWnd, определенного в библиотеке классов MFC.

ChildFrm.cpp - в этом файле находится реализация методов класса CChildFrame дочернего окна MDI. Класс CChildFrame наследуется от базового класса CMDIChildWnd, определенного в библиотеке классов MFC.

ChildFrm.h - содержит определение методов класса CChildFrame.

multi.cpp - основной файл приложения. В нем определены методы основного класса приложения CMultiApp.

multi.h - в этом файле описан главный класс приложения CMultiApp.

multiDoc.cpp - включает определение методов класса CMultiDoc.

multiDoc.h - содержит описание класса документов приложения - CMultiDoc.

multiView.cpp - содержит определение методов класса CMultiView.

multiView.h - содержит описание класса окна просмотра приложения - CMultiView.



resource.rc - файл ресурсов. В этом файле описаны все ресурсы прилржения. Сами ресурсы могут быть записаны в каталоге RES, расположенные в главном каталоге проекта.

resource.h - содержит определения идентификаторов ресурсов приложения, например, идентификаторы строк меню.

res\multi.ico - пиктограмма приложения.

res\multi.rc2 - в этом файле определены ресурсы, которые нельзя редактировать с помощью редактора ресурсов среды Visual C++.

res\multidoc.ico - пиктограмма для документов приложения.

res\toolbar.bmp - файл содержит изображение кнопок панели управления toolbar.

stdafx.h, stdafx.cpp - использование этих файлов позволяет ускорить процесс повторного построения проекта.

readme.txt - текстовый файл, содержащий описание проекта. В нем кратко рассмотрен каждый файл, входящий в проект, перечислены классы приложения, а также представлена другая информация.

Работа с документами и окнами просмотра документов


Рассмотрим некоторые процессы модификации шаблонов приложений для работы с документами.



Обработка командных сообщений


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

Порядок обработки командных сообщений гораздо сложнее. Командное сообщение, переданное для обработки объекту приложения, может последовательно передаваться другим объектам приложения. Один из объектов, класс (или базовый класс) которого содержит обработчик этого сообщения, выполняет его обработку. Например, командное сообщение, переданное главному окну приложения, в конечном счете может быть обработано активным окном просмотра.

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



Работа с графикой


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



Работа с файловой системой


В библиотеку MFC включено несколько классов для обеспечения работы с файлами. Рассматриваемые ниже классы наследуются от базового класса CFile.



Сохранение и восстановление состояния объектов


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

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

Программист может определить свой класс (на основе базового класса CObject) для работы с данными и воспользоваться рассматриваемым ниже механизмом записи и восстановления объектов.



Меню приложения


Самый простой и удобный способом создания меню приложения основан на использовании специального ресурса - шаблона меню. При создании приложения средствами MFC AppWizard однооконное приложение по умолчанию будет иметь один ресурс меню, а многооконное - два.

Для создания и изменения меню приложения следует использовать редактор ресурсов Microsoft Developer Studio. Редактор ресурсов позволяет для каждой строки меню определить ее название, идентификатор, текст подсказки, а также некоторые дополнительные характеристики.