Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык программирования Сpp 25.09.11 (2).doc
Скачиваний:
16
Добавлен:
19.08.2019
Размер:
10.09 Mб
Скачать

Функция CreateProcess

Более современной, чем WinExee, является функция API Windows CreateProcess, которая и рекомендуется для 32-разрядных приложений. В C++Builder имеется несколько вариантов этой функции. Она подключается с помощью заголовочного файла winbase.h и объявлена следующим образом:

BOOL CreateProcess(

LPCTSTR lpApplicationName, // pointer to name of executable module

LPTSTR lpCommandLine, // pointer to command line string

LPSECURITY_ATTRIBUTES lpProcessAttributes, // pointer to process security attributes

LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to thread security attributes

BOOL bInheritHandles, // handle inheritance flag

DWORD dwCreationFlags, // creation flags

LPVOID lpEnvironment, // pointer to new environment block

LPCTSTR lpCurrentDirectory, // pointer to current directory name

LPSTARTUPINFO lpStartupInfo, // pointer to STARTUPINFO

LPPROCESS_INFORMATION lpProcessInformation // pointer to PROCESS_INFORMATION

);

Функция порождает новый дочерний процесс и его первый поток (нить). В pамках этого процесса выполняется указанный файл lpApplicationName с командной строкой lpCommandLine. Впрочем, параметр lpApplicationName может быть равен NULL, а имя выполняемого модуля в этом случае должно быть первым элемент командной строки, задаваемой параметром lpCommandLine. Сам выполняемый модуль может быть любого вида: 32-разрядным приложением Windows, приложени MS-DOS, OS/2 и т.п. Однако если из приложения Windows создается проц MS-DOS, то параметр lpApplicationName должен быть равен NULL, а имя файл его командная строка включаются в lpCommandLine. Так что, как правило, что не ошибиться, проще всегда задавать lpApplicationName= NULL и помещать всю информацию в lpCommandLine.

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

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

Параметры lpProcessAttributes, lpThreadAttributes, lpEnvironment, bIheritHandles определяют наследование дочерним процессом свойств родительско процесса. Если не вдаваться в подробности наследования, то можно первые три этих параметров задавать равными NULL, а последний — false. Параметр lpCurentDirectory указывает на строку, определяющую текущий каталог и диск дочернего процесса. Это используется в приложениях-оболочках, выполняющих paзличные приложения с различными рабочими каталогами. Если параметр ран NULL, текущий каталог совпадает с родительским.

Параметр dwCreationFlags определяет флаги, задающие характеристики создаваемого процесса. Эти флаги определяют тип процесса (например, ('НК ТЕ _NEW_ CONSOLE — создание нового консольного приложения), характер и.ш

HIGHJPRIORITY_CLASS

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

IDLE_PRIOR1TY_CLASS

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

NORMAL_PRIORITY CLASS

Нормальный приоритет процесса

REALTIME_PRIORITY_CLASS

Высокий приоритет, превышающий приоритеты других процессов, включая приоритеты

Параметр lpStartupInfo указывает на структуру типа TStartupInfo, определяющую основное окно дочернего процесса. Из всех полей этой структуры обязательным для заполнения является только cb — размер в байтах данной структуры. Остальные можно не заполнять, что обеспечит вид окна по умолчанию.

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

hProcess

Возвращает дескриптор созданного процесса. Используется во

всех функциях, осуществляющих операции с объектом процесса.

hThread

Возвращает дескриптор первого потока (нити) созданного процесса. Используется во всех функциях, осуществляющих операции с объектом потока

dwProcessId

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

dwThrcadld

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

Если функция CreateProcess успешно выполнена, она возвращает ненулевое значение (true). Если произошла ошибка — возвращается 0 (false). Тогда информацию об ошибке можно получить, вызвав функцию GetLastError.

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

DWORD __fastcall WaitForlnputldle(IN HANDLE hProcess,

IN DWORD dwMilliseconds);

Параметр hProcess — дескриптор дочернего процесса, тот самый дескриптор, который в родительском процессе хранится в поле hProcess структуры lpProcessInformation. Параметр dwMilliseconds — время ожидания в миллисекундах.

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

0

Порожденный процесс инициирован и ждет ввода со стороны пользователя.

WAIT_TIMEOUT

Заданный интервал ожидания истек

OxFFFFFFFF

Произошла ошибка. Информацию о ней можно получить c помощью функции GetLastError.

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

Параметр hHandle — дескриптор дочернего процесса, тот самый дескриптор который в родительском процессе хранится в поле hProccss структуры lpInformation. Параметр dwMilliseconds — время ожидания в миллисекундах.

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

WAIT_OBJECT_0

Дочерний процесс закончился.

WAIT TIMEOUT

Заданный интервал ожидания истек, но никаких сигналов от процесса не получено, т.е. он не закончился

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

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

STARTUPINFO StartInfo = {sizeof (TStartupInfo) };

PROCESS_INFORMATION ProcInfo;

LPCTSTR s;

StartInfo.cb = sizeof (StartInfo);

StartInfo.dwFlags = STARTF_USESHOWWINDOW;

StartInfo.wShowWindow = SW_SHOWNORMAL;

if(!CreateProcess (NULL, "arj a all *.*.",

NULL, NULL, false,

CREATE_NEW_CONSOLE |

HIGH_PRIORITY_CLASS,

NULL, NULL, &StartInfo, &ProcInfo))

ShowMessage("Ошибка " + IntToStr(GetLastError()));

else {if (WaitForSingleObject(ProcInfo.hProcess, 10000) == WAIT_TIMEOUT)

ShowMessage ("3a 10 сек. архивация не завершена ");

CloseHandle(ProcInfo.hProcess) ;

}

Технология СОМ

Основные понятия СОМ

Вновь обратимся к терминологии.

Сервер – программа которая хранит какие то данные, и умеет работать с этими данными, например, осуществлять поиск. Сервер всегда находится в режиме ожидания.

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

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

Системный реестр – база данных, которая является частью операционной системы. В реестр заносится информация о всех зарегистрированных объекта и их свойствах. Реестр можно открыть с помощью команды regeadit.exe из командной строки.

COM (Component Object Model — объектная модель компонентов; произносится как [ком]) — это технологический стандарт от компании Microsoft, предназначенный для создания программного обеспечения на основе взаимодействующих компонентов, каждый из которых может использоваться во многих программах одновременно.

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

COM-интерфейсы — наборы абстрактных функций и свойств. Каждый COM-компонент должен, как минимум, поддерживать стандартный интерфейс IUnknown, который предоставляет базовые средства для работы с компонентом. Интерфейс IUnknown включает в себя три метода: QueryInterface, AddRef, Release.

Технология COM (Component Object Model — компонентная модель объектов) предоставляет возможность одной программе (клиенту) работать с объектом другой программы (сервера). СОМ — это модель объекта, которая предусматривает полную совместимость во взаимодействии между компонентами, написанными разными компаниями и на разных языках. При этом неважно, где выполняются программы: в одном потоке, в разных потоках, на разных компьютерах.

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

Сервером может быть исполняемый файл или библиотека DLL. При установке сервера в Windows в системный реестр заносится информация о всех его объектах. Эта информация включает в себя идеен тификатор класса CLSID (Class Identifier), однозначно определяющий класс объекта. Заносится информация о типе сервера:

внутренний (in-process — внутри процесса) — DLL, подключающаяся к клиенту,

локальный (local) — работающий отдельным процессом на компьютере клиента,

удаленный (remote) — работающий на удаленном компьютере.

Для внутренних и локальных серверов в реестр заносится полное имя файла, а для удаленных — полный сетевой адрес. Таким образом, в системе хранится вся информация о сервере СОМ, необходимая для вызова его в нужный момент.

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

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

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

Все хорошо, но откуда разработчик клиентского приложения может получить информацию об объектах СОМ, их интерфейсах, свойствах, методах, параметрах методов? Вся эта информация содержится в библиотеке типов, которая создается разработчиком объекта СОМ и распространяется вместе с объектом. Библиотека создается с помощью языка описания интерфейса IDL (Interface Definition Language).

Теперь рассмотрим подробнее интерфейсы. Каждый интерфейс имеет имя, начинающееся с символа «I», и GUID -- глобальный уникальный идентификатор (Globally Unique Identifier). Подобные GUID создаются и используются не только дляинтерфейсов. Для интерфейсов GUID называется IID. Каждый объект СОМ имеет интерфейс IUnknown. Этот интерфейс имеет всего три метода:

Querylnterface - получение указателя на интерфейс,

AddRef - увеличение на 1 числа ссылок на объект,

Release - уменьшение на 1 числа ссылок на объект.

Querylnterface возвращает указатель на интерфейс с заданным IID. Метод Release должен вызываться по окончании работы с интерфейсом, чтобы уведомить объект, что данный клиент в нем более не нуждается. Эти два метода используются всегда при работе с объектом СОМ. Еще один метод — AddRef используется только в тех случаях, когда один клиент передал другому ссылку на интерфейс. Поскольку при этом метод AddRef, автоматически увеличивающий число ссылок, не вызывается, клиент, которому передана ссылка, должен вызвать AddRef, чтобы доложить объекту, что он тоже с ним работает. Все остальные интерфейсы объектов являются наследниками IUnknown и наследуют те же три метода, добавляя, конечно, свои собственные. Так что сказанное выше применимо к любым интерфейсам. Среди этих интерфейсов-наследников необходимо отметить IDispatch. Это интерфейс, используемый клиентами серверов автоматизации OLE для доступа к методам и свойствам объекта.

Описанный способ работы с объектом СОМ предполагает существование экземпляра этого объекта и знание клиентом указателя на него. Если же речь идет о создании первого экземпляра объекта, то сначала надо обратиться к библиотеке СОМ, обеспечивающей диспетчеризацию работы с объектами. Имена всех функ ций библиотеки начинаются с «Со». Создание объекта осуществляется вызовомфункции CoCreateInstance этой библиотеки и передачей в нее CLSID требуемого класса, IID интерфейса и требуемого типа сервера. Библиотека СОМ обращается к системному реестру, в котором, как было описано выше, хранится информация о сервере, запрашивает его и возвращает требуемый указатель.

Если говорить о деталях, то библиотека СОМ по переданному ей CLSID обращается к соответствующей фабрике класса — специальному объекту, управляющему созданием экземпляров требуемых объектов. Фабрика класса поддерживает интерфейс ICIassFactory. Вызов фабрики класса осуществляется функцией CoGetClassObject, в которую передается CLSID требуемого класса и IID интерфейса ICIassFactory. Функция возвращает указатель на интерфейс требуемой фабрики класса. С помощью этого указателя вызывается метод CoCreatelnstance интерфейса фабрики класса с передачей в него IID интерфейса. Эта функция создает экземпляр объекта и возвращает указатель на его интерфейс, который библиотека СОМ возвращает клиенту.

Технология СОМ реализуется специальными библиотеками, включая OLE32.dll и OLEAut32.dll. Они содержатстандартные интерфейсы и API с функциями, обеспечивающими создание объектов СОМ и управление ими. Так как технология СОМ не зависит от языка, в ней используются типы, отличные от других языков. Прежде всего, это относится к строкам, которые в разных языках описываются по-разному. В СОМ используется свой строковый тип - BSTR (Basic STRing). Он описывает строку, в начале которой указана ее длина. Поскольку длина строки известна, завершающего нулевого символа не требуется.

Реализация внутреннего сервера СОМ и его клиента

Теперь посмотрим, как все изложенное в разделе 7.8.1 реализуется в C++Builder.

Для работы со спецификациями СОМ используются библиотеки шаблонов Microsoft Active Template Library (ATL) с модифицированными классами и макросами. Как всегда, C++Builder выполняет за вас всю черновую работу, не требуя глубокого проникновения в механизм СОМ.

Рассмотрим методику создания внутреннего сервера. Он реализуется библиотекой DLL, которая регистрируется в системе и вызывается при необходимости клиентскими приложениями. Библиотека может содержать какие-то методы вычислений, используемые различными клиентами, поддерживать связь с базой данных и реализовывать бизнес-логику,обеспечивать связь клиентов

друг с другом и выполнять многие другие функции.

Для создания в С++Builder внутреннего сервера СОМ, выполните, прежде всего, команду File|New | Other.

На странице ActiveX Депозитария выберите пиктограмму ActiveX Library.Получите примерно следующее

Сохраним проект Project.cpp под именем ProjectMyComServ. Для этого воспользуемся командой File|SaveProjectAs… . Сначала нужно сохранить файлы связанные с проектам под иенем MyComServ, а потом сам проект ProjectMyComServ.

Для этого проекта С++Builder создаст ряд модулей, которые вы можете увидеть в окне Редактора Кода. Но мы не будем анализировать их коды. Вполне можно обойтись без глубокого рассмотрения кодов.

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

Выполните опять команду File|New|Other и на странице ActiveX Депозитария выберите пиктограмму Automation Object — объект автоматизации. Перед вами откроется окно,

В окне CoClass Name вы должны указать имя класса создаваемого объекта —MyObject.

Выпадающий список Threading model определяет способы вызова клиентами интерфейса объекта СОМ в многопоточных приложениях:

Single

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

Apartment

Все вызовы клиентов обрабатываются в одном потоке, в котором был создан объект. Каждый компонент может иметь свою секцию (apartment) потока. Надо принимать меры для защиты глобальных данных.

Free

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

Both

То же, что Free, только откаты осуществляются гарантированно в том же потоке.

Neutral

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

Окно Description дает возможность ввести текстовое описание объекта СОМ. Индикатор Generate Event support code указывает на необходимость реализации отдельного интерфейса для управления событиями вашего объекта автоматизации. Мы не будем заниматься событиями, так что флажок нужно снять.

После того, как вы заполните всю информацию в окне и щелкнете на ОК, перед вами откроется окно Редактора Библиотеки Типов. В дальнейшем в любой момент можете вызвать это окно командой View | Type Library. Эта команда доступна только в том случае, если ваш проект содержит библиотеку типов.

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

В первый момент в левой панели будет всего три вершины: библиотеки MyComServ, введенного в нее объекта автоматизации MyObject и его интерфейса по умолчанию IMyObject. Остальные вершины нам еще предстоит создать.

Выделите вершину интерфейса IMyObject и посмотрите связанную с ней страницу Attributes в правой панели окна. В выпадающем списке Parent Interface вы можете задать для вашего интерфейса родительский интерфейс. В нашем случае выберем базовый интерфейс IUnknown, о котором говорилось ранее.

Теперь займемся разработкой его методов. Щелкните на быстрой кнопке New Method (пятая справа на рис) или выберите аналогичный раздел из контекстного меню. В дереве левой панели появится вершина нового метода. Назовите этот метод Add — он будет складывать два числа. Выделите вершину метода и перейдите в правой панели на страницу Parameters, которая показана на рис. В выпадающем списке Return Type надо выбрать тип возвращаемого методом значения. Впрочем, для создаваемого нами сервера автоматизации вариантов нет — возвращаемое значение должно иметь тип HRESULT. Этот тип определен как long — целое. Так что выбор иного типа приведет в дальнейшем к сообщению об ошибке. Впрочем, для функции Add тип HRESULT подходит, так как она должна возвращать целое значение. Для задания параметров метода нажмите кнопку Add. В нижнем окне панели появится строка, соответствующая вводимому параметру. В столбце Name надо задать имя параметра (Number1 на рис.). В столбце Туре надо выбрать из выпадающего списка тип параметра (long). В столбце Modifier можно выбрать модификаторы параметра: входной, выходной, необязательный, со значением по умолчанию и т.п. В нашем примере можно оставить значение по умолчанию: [in]. Аналогичным образом надо задать второй параметр.

Теперь надо ввести в объект второй интерфейс. Щелкните на быстрой кнопке New Interface (крайняя левая). В левой панели появится новая вершина. Назовите новый интерфейс IntDiv. Пусть он будет наследовать класс IUnknown, как и наш первый интерфейс. Введите в новом интерфейсе так же, как делали раньше, метод Div — деление и задайте для него два входных параметра (рис.). Тип результа та опять должен быть HRESULT. Но в данном случае по замыслу метод должен возвращать действительное число. Так что нам надо добавить в метод еще один параметр — выходной. Выходной параметр (назовите его Res) должен быть указателем. Поэтому вводя этот параметр, выберите в столбце Туре тип float и добавьте после него символ "*", как показано на рис.

Затем щелкните на выпадающем списке в столбце Modifier, откроется окно, показанное на рис. Индикатор In включается для входных параметров, т.е. параметров, передаваемых от клиента серверу. Индикатор Out включается для выходных параметров, передаваемых от сервера клиенту. В нашем случае надо включить только этот индикатор. Отметим, что для некоторых параметров можно включать оба индикатора.

Итак, описали второй интерфейс. Теперь надо ввести его в объект. Выделите в левой панели вершину объекта MyObject и перейдите в правой панели на страницу Implements. Щелкните на ней правой кнопкой мыши и выберите в контекстном меню раздел Insertlnterfoce.

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

Описание объекта закончено. Теперь надо написать реализацию введенныхметодов. Щелкните на быстрой кнопке Refresh Implementation — обновить реализацию (третья справа). Перейдите в Редактор Кода. Можете вызвать Менеджер Проектов, чтобы увидеть структуру файлов, которые включились в проект. Впрочем, из этих многочисленных файлов в данном случае васинтересует только один — файл реализации MyObjectlmpl.cpp. В нем вы увидите заготовки

реализации ваших методов:

STDMETHODIMP TMyObjectlmpl::Add(long Numberl, long Number2)

STDMETHODIMP TMyObjectlmpl::Div(long Numberl, long Number2, float* Res)

{

}-

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

STDMETHODIMP TMyObjectlmpl::Add(long Numberl, long Number2)

{

return Number1 + Number2;

}

.STDMETHODIMP TMyObjectlmpl::Div(long Numberl, long Number2, float* Res)

{

*Result = (float)Number1 / Number2;

return S_OK;

}

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

Теперь все сделано, объект в составе сервера СОМ создан. Осталось зарегистрировать сервер в системе. Это можно сделать командой Run|Register ActiveX Server главного меню C++Builder. To же самое можно выполнить второй справа быстрой кнопкой в окне рис.

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

Run|Unregister ActiveX Server.

Отметим еще, что для распространения вашего проекта между пользователями потребуется библиотека типов на языке IDL Трансляция ее осуществляется быстрой кнопкой Export To IDL — крайняя правая на рис..

Осталось проверить работу созданного сервера, создав для него клиентское приложение. Начните новый проект. Разместите на форме два окна Edit, в которых пользователь сможет задавать числа. Добавьте две кнопки, одна из которых (назовите ее BAdd) будет выполнять сложение, а другая (BDiv) — деление, выполняя соответственно методы первого и второго интерфейсов. Введите также в приложение метку Label, в которой будут отображаться результаты эти вычислений. Теперь нам надо обеспечить связь нашего клиентского приложения с сервером. Поскольку вы сами разработали сервер и все его файлы у вас имеются, это можно было бы сделать очень просто. Но мы рассмотрим более общий путь, обеспечивающий связь с любым сторонним сервером. Выполните команду Project|Import Type Library. Перед вами откроется окно, показанное на рис.

В верхнем списке вы увидите все зарегистрированные на вашем компьютере библиотеки типов. Если какой-то библиотеки вы не найдете, можно щелкнуть на кнопке Add и выбрать требуемый файл. В нашем случае, если вы не забыли зарегистрировать ваш сервер, вы найдете его библиотеку в списке, как показано на рис. Выключите индикатор Generate Component Wrappers. Это исключит генерацию оболочки, которая нам не нужна. Щелкните по имени созданного типа. Кнопка Create Unit - создать модуль, станет активной. Далее щелкните на кнопке Create Unit. В результате в проект включится модуль Mycomserv_tlb. Это модуль описания вашей библиотеки типов, файлы которого мы пока не смотрели. Собственно, можете и сейчас в них не всматриваться. В файле Mycomserv_tlb.cpp вы увидите список идентификаторов GUID библиотеки, объекта и интерфейсов. А в файле Мусотserv_tlb.h вы найдет описание класса с многочисленными объявлениями методов и, в частности, объявлениями введенных вами методов Add и Div.

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

#include <MyComServ_TLB.h>

Теперь вы можете использовать объявления, содержащиеся в описании сервера. В объявление класса формы клиентского приложения введите переменные Interface1 и Interface2, которые будут указывать на соответствующие интерфейсы сервера. Объявление класса формы может иметь вид:

#include <MyComServ_TLB.h>

. . .

public: . // User declarations

fastcall TForml(TComponent* Owner);

TCOMIMyObject Interfacel;

IntDivPtr Interface2;

};

Тип переменной Interface1 задан как TCOMIMyObject. Это класс так называемого интеллектуального (smart) интерфейса. Вы можете найти имя этого класса (точнее, имя частной реализации соответствующего шаблона класса) в заголовочном файле MyComSeru_TLB.h. Впрочем, не обязательно вдаваться в тонкости реализации. Достаточно знать, что интересующее вас имя складывается из имени интерфейса, перед которым добавляется префикс ТСОМ. Интеллектуальный интерфейс автоматически осуществляет операции по установлению и разрыву связи с сервером. Так что предпочтительнее использовать именно его, а не сам интерфейс. Тип переменной Interface2 задан равным IntDivPtr. Это указатель на сооветствующий интерфейс. Он представляет собой реализацию одного из шаблонов класса, который вы можете найти в файле MyComServ_TLI}.h. Имя типа указателя складывается из имени интерфейса и суффикса Ptr.

В обработчик события формы OnCreate вставьте код:

Interfacel = CoMyObject::Create ( ) ;

Interface2 = Interfacel;

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

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

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

void fastcall TForml::BAddClick(TObject *Sender)

{

Label1->Caption = Interfacel.Add(StrToInt(Edit1->Text),

StrToInt(Edit2->Text));

и

void fastcall TForml::BDivClick(TObject *Sender)

float R;

(*Interface2).Div(StrToInt(Edit1->Text),

StrToInt(Edit2->Text), &R);

Label1->Caption = R;

}

Как видно в приведенном коде, вызов метода Add осуществляется непосредственно через объект Interface1. Причем результат этого вызова возвращаемое методом значение, в нашем случае — сумма чисел. Вызов метода второго интерфейса осуществляется с учетом того, что переменная Interface2 — указатель на интерфейс. Кроме того, в приложение приходится вводить дополнительную переменную R, указатель на которую передается в метод Div. В переменную R заносится результат — значение выходного параметра метода.

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

Реализация и использование локальных серверов СОМ

Ранее рассматривалось построение внутреннего сервера СОМ в виде файла DLL. Теперь рассмотрим построение сервера, реализуемого исполняемым файлом .ехе. С точки зрения пользователя основное отличие этого варианта заключается в том, что сервер представляет собой отдельное приложение. За его состоянием можно наблюдать, можно управлять его работой, переключать его в различные режимы и т.п.

Попробуем построить простой локальный сервер СОМ и его клиентов. Пусть этот сервер будет содержать окно редактирования, в которое можно заносить какой-то текст сообщения. А клиенты сервера смогут читать это сообщение и заносить на сервер собственные сообщения. Таким образом, клиенты смогут общаться друг с другом. Чтобы дополнительно обсудить создание и использование методов интерфейсов, добавим на сервер метод, который по запросу клиента может управлять видимостью сервера. Начните новое приложение, перенесите на форму окно редактирования Edit и задайте заголовок формы Сервер MyServer. Больше можете в серверное приложение ничего не вносить. А можете добавить какие-то органы управления по своему желанию.

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

Пока создаy обычный проект C++Builder. Теперь давайте превратим его в локальный сервер СОМ. Выполните команду File | New | Other и на странице ActiveX Депозитария выберите пиктограмму Automation Object — объект автоматизации. С ним вы уже умеете работать, поскольку точно так же создавали объект во внутреннем сервере, рассмотренном ранее. В окне, которое откроется перед вами в окошке CoClass Name укажите имя класса MyObject.

После щелчка на кнопке ОК перед вами откроется знакомое окно РедактораБиблиотеки Типов

Что нужно вам внести в интерфейс? Имя этого интерфейса в нашем примере IMyServer, а класс его родительского интерфейса надо установить равным IDispatch. Прежде всего, введите в этот интерфейс метод SetVis. Этот метод будет управлять видимостью сервера. Задайте в методе один параметр с именем Vis, типом VARIANT_BOOL (это булев тип, используемый в СОМ) и спецификатором [in]. Этот параметр будет указывать, должен ли сервер быть видимым (при значении true) или невидимым (при значении false).

Теперь давайте введем в интерфейс свойство с именем Mess. Оно будет представлять читаемые или записываемый текст сервера. Поскольку пока мы не добавляли свойств в интерфейс, рассмотрим эту операцию подробнее. Нажмите кнопочку выпадающего списка рядом с кнопкой Property (четвертая справа). Откроется меню с разделами Read | Write (для чтения и записи), Read Only (только для чтения), Write Only (только для записи), Read | Write | Write By Ref (для чтения, записи и записи по ссылке). Нам надо и читать, и записывать значения Mess. Так что выберите раздел Read | Write. В результате в левой панели Редактора Библиотеки Типов появятся две вершины: одна для функции записи, другая для функции чтения. Задайте одной из них имя Megs. Это же имя автоматически присвоится и второй вершине. На странице Attributes задайте тип свойства — BSTR. Это тип строк, используемый в СОМ.

Для функции чтения на странице Parameters задайте имя параметра функции Text, тип параметра BSTR * — указатель на BSTR, спецификатор параметра [out, retval]. Для функции записи имя параметра Text, тип параметра BSTR, спецификатор параметра [in]. Возвращаемый тип в обеих функциях HRESULT.

Формирование свойств и методов интерфейса закончено. Теперь можно их реализовывать. Щелкните на кнопке Refresh Implementation (Обновить реализацию) — третья справа на рис.. Перейдите в Редактор Кода. В файле MyServerlmpl.cpp вы увидите заготовки функций записи и чтения get_Mess, set_Mess и метода SetVis. Вам остается только добавить по одному оператору в каждую из этих заготовок. В результате они должны выглядеть так:

STDMETHODIMP TMyServerlmpl::get_Mess(BSTR* Text)

{

try

(

*Text = WideString((Forml->Editl->Text).c_str()). cjDstr();

}

catch(Exception &e)

(

return Error(e.Message.c_str(), IID_IMyServer);

}

return SJDK;

STDMETHODIMP TMyServerlmpl::set_Mess(BSTR Text)

{

try

{

Forml->Editl->Text = Text;

}

catch(Exception se)

{

return Error(e.Message.c_str(), IID_IMyServer);

}

return S_OK;

};

STDMETHODIMP TMyServerlmpl::SetVis(VARIANT_BOOL Vis)

{

Forml->Visible = Vis;

return S_OK;

}

Коды функции set_Mess очевиден: в окно Editl записывается текст, передан-

ный в функцию как параметр Text. Реализация функции Set Vis также очевидна:

видимость формы устанавливается равной переданному в функцию булеву пара-

метру Vis. А вот реализация функции get_Mess требует пояснений. Вам надо зане-

сти в переданный по ссылке параметр текст, содержащийся в окне Editl. Этот

текст имеет тип AnsiString, а вам надо преобразовать его в тип BSTR. Приведен-

ный оператор осуществляет это преобразование следующим образом. Сначала с по-

мощью c_str() — функции-элемента класса AnsiString текст преобразовывается

в тип char *. Затем результат преобразования передается в конструктор класса

WideString. В результате получается строка символов Unicode. И, наконец, с по-

мощью c_bstr() — функции-элемента класса WideString строкапреобразовывает-

ся в тип BSTR.

Проектирование сервера закончено. Можете сохранить проект, откомпилиро-

вать и выполнить. В момент запуска сервер автоматически зарегистрируется в сис-

теме: в реестр Windows запишутся его идентификаторы и место размещения файла.

Теперь можно реализовывать клиентское приложение. Начните новый проект

и создайте форму, возможный вариант которой показан на рис. 7.15 а.

Кнопка Принять (BGetMess) должна обеспечивать отображение в окне редакти-

рования Editl текст сообщения, записанного на сервере. Кнопка Послать (BSetMess)

должна обеспечивать передачу текста из окна редактирования на сервер. Кнопка Ви-

димость (BSetVis) должна управлять видимостью сервера в зависимости от состоя-

ния индикатора Видимый (CheckBoxl). Ниже приведен возможный вариант кода

клиентского приложения.

Variant Serv;

void fastcall TForml::FormCreate(TObject *Sender)

f

Serv = CreateOleObject("Projectl.MyServer");

)/

/

void fastcall TForml::BGetMessClick(TObject *Sender)

(

Editl->Text = Serv.OlePropertyGet("Mess");

)

//

void fastcall 'TForml::BSetMessClick(TObject *Sender)

Процессы, потоки, распределенные приложения 489

Serv.OlePropertySet("Mess",

WideString((Editl->Text) .c_str()) .c_bstr ( ) ) ;

}

/ /

void fastcall TForml::BSetVisClick(TObject ≪Sender)

{

Serv.deprocedure("SetVis", CheckBoxl->Checked);

}

В приложении вводится глобальная переменная Serv — объект сервера. Тип

этой переменной — Variant. Первая процедура приведенного кода FormCreate яв-

ляется обработчиком события OnCreate формы. В этой процедуре функцией

CreateOleObject в Serv создается объект OLE интерфейса, по которому можно свя-

зываться с сервером. Имя сервера и его объекта передается в CreateOleObject в ка-

честве параметра. Все необходимые операции по связи с сервером выполняются ав-

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

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

Функция BGetMessClick читает сообщение с сервера в окно Editl. Для досту-

па к значению свойства Mess используется функция OlePropertyGet. В качестве

параметра в нее передается строка с именем читаемого свойства. Функция BSet-

MessClick посылает на сервер в свойство Mess текст из окна Editl. Делается это

с помощью функции OlePropertySet. Первым параметром в эту функцию переда-

ется строка с именем свойства, значение которого надо установить. Вторым пара-

метром передается устанавливаемое значение. В данном случае осуществляется

уже рассмотренное при проектировании сервера преобразование типа AnsiString

в тип BSTR.

Функция BSetVisCHck выполняет метод SetVis сервера. Для выполнения ме-

тода используется функция OleProcedure. В качестве первого параметра в нее пе-

редается строка с именем вызываемого метода. А последующие параметры содер-

жат значения параметров метода. В нашем случае надо передать только один пара-

метр — булеву величину CheckBoxl—>Checked, указывающую, должен ли сервер

бытьвидимым, или нет.

Сохраните проект и выполните его. Вы увидите, что при этом автоматически за-

пустится сервер, и клиент сможет им управлять: заносить в него свои сообщения,

принимать сообщения, записанные в окне сервера, устанавливать видимость серве-

ра. Как только вы закроете клиентское приложение, сервер тоже завершит работу.

Чтобы полностью уяснить взаимодействие сервера и клиентов, запустите сред-

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

все они работают с одним экземпляром сервера и могут обмениваться через него со-

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

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

Можете провести еще один эксперимент, который покажет, как регистрирует-

ся сервер. Перенесите средствами Windows исполняемый файл сервера Projectl.exe

в какой-то другой каталог. При попытке выполнить клиентское приложение будет

выдано сообщение об ошибке: ≪Не удается найти указанный файл≫. Это естествен-

но, так как в реестре записан каталог, в котором должен находиться сервер, а те-

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

ложение сервера из нового каталога, и его работоспособность восстановится. Сер-

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

каталогах, смогут с ним связываться.

Проведенный эксперимент показал не только особенности регистрации серве-

ра, но и определенные недостатки нащего клиентского приложения. Оно не застра-

ховано от сбоев. Один из сбоев вы видели при попытке запустить незарегистриро-

ванный или неправильно зарегистрированный сервер. Другой сбой наступит, если

клиент связан с сервером, а в это время кто-то этот сервер закрыл. Правда, при по-

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