|
Программирование >> Арифметические и логические операции
придание им способности взаимодействовать с внешней средой, предоставление пользователю максимального эффекта в работе с приложениями. Не равняясь на такие гранды технической документации, как MSDN, тем не менее, хотим указать на путь, по которому сегодня проектируются современные приложения. Автоматизация как есть Автоматизация (Automation) была изначально создана как способ для приложений (таких как Word или Excel) предоставлять свою функциональность другим приложениям, включая скрипт-языки. Основная идея заключалась в том, чтобы обеспечить наиболее удобный режим доступа к внутренним объектам, свойствам и методам приложения, не нуждаясь при этом в многочисленных хедерах и библиотеках. Вообще, можно выделить два вида автоматизации - внешнюю и внутреннюю. Внешняя автоматизация - это работа сторонних приложений с объектной моделью вашей программы, а внутренняя - это когда сама программа предоставляет пользователю возможность работы со своей объектной структурой через скрипты. Комбинирование первого и второго вида представляется наиболее масштабируемым решением и именно о нем пойдет речь. Для начала обратим внимание на самое дно - интерфейсы COM. Если термин интерфейс в этом контексте вам ничего не говорит, то представьте себе абстрактный класс без реализации - это и есть интерфейс. Реальные объекты наследуются от интерфейсов. Компоненты, наследующиеся от интерфейса lUnknown, называются COM-объектами. Этот интерфейс содержит методы подсчета ссылок и получения других интерфейсов объекта. Автоматизация базируется на интерфейсе IDispatch, наследующегося от IUnknown. IDispatch позволяет запускать методы и обращаться к свойствам вашего объекта через их символьные имена. Интерфейс имеет немного методов, которые являются тем не менее довольно сложными в реализации. К счастью, существует множество шаблонных классов, предлагающих функциональность интерфейса IDispatch, поэтому для создания объекта, готового к автоматизации, необходимо всего лишь несколько раз щелкнуть мышкой в ClassWizard Visual C++. Что касается способа доступа и динамического создания ваших внутренних dispatch объектов, то тут тут тоже все довольно просто - данные об объекте хранятся в реестре под специальным кодовым именем, которое называется Progld. Например, progid программы Excel - Excel.Application. Создать в любой процедуре на VBScript достаточно легко - надо только вызвать функцию CreateObject, в которую передать нужный ProglD. Функция вернет указатель на созданный объект. В MFC существует специальный класс, под названием CCmdTarget. Наследуя свои классы от CCmdTarget, вы можете обеспечить для них необходимую функциональность в dispatch виде - как раз как ее понимают скрипты. При создании нового класса в ClassWizard (View * ClassWizard Add Class * New), наследуемого от CCmdTarget, просто щелкните на кнопке Automation или Creatable by ID, чтобы обеспечить возможность создания экземпляра объекта по его ProglD. Заметим, что для программ, реализующих внутреннюю автоматизацию, это не нужно. Для приложений, реализующих внешнюю и смешанную автоматизацию, это необходимо для корневых объектов. COM-объекты можно создавать только динамически. Это связано с тем, что объект может использоваться несколькими приложениями одновременно, а значит, удаление объекта из памяти не может выполнить ни одно из них. Разумно предположить, что объект сам должен отвечать за свое удаление. Такой механизм реализован при помощи механизма ссылок (reference count). Когда приложение получает указатель на объект, он увеличивает свой внутренний счетчик ссылок, а когда приложение освобождает объект - счетчик ссылок уменьшается. При достижении счетчиком нуля, объект удаляет сам себя. Если наш объект был создан по ProgID другим приложением, то программа CTestApp (другими словами, Automation-Server) не завершится до тех пор, пока счетчик ссылок CTestAutomatedClass не станет равным нулю. Создаваемые через ProgID COM-объекты, обычно являются Proxy-компонентами. Реально они не содержат никакой функциональности, но имеют доступ к приложению и его внутренним, не доступным извне, функциям. Хотя можно организовать все таким образом, чтобы всегда создавался только один COM-объект, а все остальные вызовы на создание возвращали указатели на него. Метод интерфейса CCmdTarget GetIDispatch(), позволяет получить указатель на реализованный интерфейс IDispatch. В параметрах можно указать, нужно ли увеличивать счетчик ссылок или нет. Оболочка из классов для COM Программировать с использованием COM настолько трудно, что вы не должны даже пробовать это без MFC. Правильно или неправиль- но? Абсолютная чушь! Рекламируемые OLE и его преемник COM имеют элегантность гиппопотама, занимающегося фигурным катанием. Но размещение MFC на вершине COM подобно одеванию гиппопотама в клоунский костюм еще больших размеров. Итак, что делать программисту, когда он столкнется с потребностью использовать возможности оболочки Windows, которые являются доступными только через интерфейсы COM? Для начала, всякий раз, когда вы планируете использовать COM, вы должны сообщить системе, чтобы она инициализировала COM подсистему. Точно так же всякий раз, когда вы заканчиваете работу, вы должны сообщить системе, чтобы она выгрузила COM. Самый простой способ это сделать заключается в определении объекта, конструктор которого инициализирует COM, а деструктор выгружает ее. Самое лучшее место, для внедрения данного механизма - это объект Controller. class Controller public: Controller (HWND hwnd, CREATESTRUCT * pCreate); Controller (); private: UseCom comUser; Im a COM user Model model; View view; HINSTANCE hInst; }; Этот способ гарантирует, что COM подсистема будет проинициа-лизирована прежде, чем к ней будут сделаны любые обращения и что она будет освобождена после того, как программа осуществит свои разрушения (то есть, после того, как Вид и Модель будут разрушены). Класс UseCom очень прост. class UseCom public: UseCom () HRESULT err = CoInitialize (0); if (err != S OK) throw Couldnt initialize COM ; ~UseCom () CoUninitialize (); Пока не было слишком трудно, не так ли? Дело в том, что мы не коснулись главной мерзости COM программирования - подсчета ссылок. Вам должно быть известно, что каждый раз, когда вы получаете интерфейс, его счетчик ссылок увеличивается. И вам необходимо явно уменьшать его. И это становится более чем ужасным тогда, когда вы начинаете запрашивать интерфейсы, копировать их, передавать другим и т.д. Но ловите момент: мы знаем, как управляться с такими проблемами! Это называется управлением ресурсами. Мы никогда не должны касаться интерфейсов COM без инкапсуляции их в интеллектуальных указателях на интерфейсы. Ниже показано, как это работает. template <class T>class SIfacePtr public: ~SIfacePtr () Free (); T * operator->() { return p; } T const * operator->() const { return p; } operator T const * () const { return p; } T const & GetAccess () const { return * p; } protected: SIfacePtr () : p (0) {} void Free () if ( p != 0) p->Release (); p = 0; T * p; private: SIfacePtr (SIfacePtr const & p) {} void operator = (SIfacePtr const & p) {} Не волнуйте, что этот класс выглядит непригодным (потому что имеет защищенный конструктор). Мы никогда не будем использовать его непосредственно. Мы наследуем от него. Между прочим, это удобный прием: создайте класс с защищенным пустым конструктором, и осуществите наследование от него. Все наследующие классы должны обеспечивать их внутреннюю реализацию своими открытыми конструкторами. Поскольку вы можете иметь различные классы, наследующие от SIfacePtr, они будут отличаться по способу получения, по их конструкторам, рассматриваемым интерфейсам. Закрытый фиктивный копирующий конструктор и оператор = не всегда необходимы, но они сохранят вам время, потраченное на отладку, если по ошибке вы передадите интеллектуальный интерфейс по значению вместо передачи по ссылке. Это больше невозможно. Вы можете освободить один и тот же интерфейс, дважды и это будет круто отражаться на подсчете ссылок COM. Верьте, это случается. Как это часто бывает, компилятор откажется передавать по значению объект, который имеет закрытую копию конструктора. Он также выдаст ошибку, когда вы пробуете присвоить объект, который имеет закрытый оператор присваивания. Оболочки API часто распределяет память, используя свои собственные специальные программы распределения. Это не было бы настолько плохо, если бы не предположение, что они ожидают от вас освобождения памяти с использованием той же самой программы распределения. Так, всякий раз, когда оболочка вручает нам такой сомнительный пакет, мы оборачиваем его в специальный интеллектуальный указатель. template <class T> class SShellPtr public: ~SShellPtr () { Free (); malloc->Release (); T * weak operator->() { return p; } T const * operator->() const { return p; } operator T const * () const { return p; } T const & GetAccess () const { return * p; } protected: SShellPtr () : p (0) Obtain malloc here, rather than in the destructor. Destructor must be fail-proof. Revisit: Would static IMalloc * shellMalloc work? if (SHGetMalloc (& malloc) == E FAIL) throw Exception Couldnt obtain Shell Malloc ; void Free () if ( p != 0) malloc->Free ( p); p = 0; T * p; IMalloc * malloc; private: SShellPtr (SShellPtr const & p) {} void operator = (SShellPtr const & p) {} Обратите внимание на использование приема: класс SShellPtr непосредственно не пригоден для использования. Вы должны наследовать от него подкласс и реализовать в нем соответствующий конструктор. Обратите также внимание, нет уверенности, может ли shellMalloc быть статическим элементом SShellPtr. Проблема состоит в том, что статические элементы инициализируются перед WinMain. Из-за этого вся COM система может оказаться неустойчивой. С другой стороны, документация говорит, что вы можете безопасно вызывать из другой API функции CoGetMalloc перед обращением к CoInitialize. Это не говорит о том, может ли SHGetMalloc, который делает почти то же самое, также вызываться в любое время в вашей программе. Подобно многим другим случаям, когда система ужасно разработана или задокументирована, только эксперимент может ответить на такие вопросы. Между прочим, если вы нуждаетесь в интеллектуальном указателе, который использует специфическое распределение памяти для COM, то получите его, вызывая CoGetMalloc. Вы можете без опаски сделать этот malloc статическим элементом и инициализировать его только один раз в вашей программе (ниже SComMalloc:: GetMalloc тоже статический): IMalloc * SComMalloc:: malloc = SComMalloc::GetMalloc (); IMalloc * SComMalloc::GetMalloc () IMalloc * malloc = 0; if (CoGetMalloc (1, & malloc) == S OK)
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |