Королевство Дельфи"Knowledge itself is power"
F.Bacon
 Лицей
  
Главная
О лицее

Список семинаров

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  04:29[Войти] | [Зарегистрироваться]

Урок 9. COM-сервер средствами Delphi

Антон Григорьев
дата публикации 18-05-2007 03:20

урок из цикла: Использование COM/DCOM в Delphi


предыдущий урок содержание семинара следующий урок

Урок 9. Теперь перейдём к созданию простейшего COM-сервера средствами Delphi. Как мы уже упоминали, Delphi предоставляет мощные средства реализации COM-серверов, позволяющие не думать о многих низкоуровневых вещах. На этом уроке мы в этом наконец-то убедимся. Мы создадим два сервера — обычный и синглетон, которые реализуют уже знакомый нам интерфейс ILowLevelServer.

Как и в прошлом уроке, наши серверы будут внутренними, а клиент должен будет работать с ними только из одной нити. Но не потому, что средства Delphi не позволяют создавать более гибкие и универсальные серверы, а всё по той же причине — мы ещё не рассматривали соответствующие аспекты COM/DCOM, поэтому пока вынуждены ограничиться простейшими случаями.

Итак, начнём. Мы здесь будем показывать создание COM-сервера в среде Delphi 7. Те окна, скриншоты которых представлены здесь, в других версиях Delphi могут выглядеть несколько иначе, но последовательность действий остаётся той же.

Сначала нужно создать новый проект — COM-сервер в виде библиотеки. Для этого в меню выбираем File/New/Other, и в открывшемся окне выбираем закладку ActiveX. На ней нас интересует ActiveX Library (рисунок 1).


Рисунок 1 — Создание нового проекта

Во вновь созданном проекте будет только один dpr-файл, содержащий до смешного лаконичный код:

library Project2;

uses
  ComServ;

exports
  DllGetClassObject,
  DllCanUnloadNow,
  DllRegisterServer,
  DllUnregisterServer;

{$R *.RES}

begin
end.

Из этого кода сразу видно, что все функции, которые должен экспортировать внутренний COM-сервер, уже реализованы в модуле ComServ. Чуть позже мы познакомимся с этим модулем поближе, а пока продолжим создание сервера. Для этого снова открываем окно, показанное на рисунке 1, и выбираем там COM Object. После этого запускается эксперт, который отображает окно, показанное на рисунке 2.


Рисунок 2 — Окно эксперта создания COM-объекта

На рисунке 2 поля заполнены именно так, как необходимо для нашего простейшего COM-сервера. Поле ClassName — это имя кокласса. Имя реализующего его класса Delphi образуется из этого имени добавлением префикса "T". Поле Instancing управляет тем, как будет создаваться COM-объект. Имеются три варианта: Internal, Single Instance и Multiple Instance. COM-объект, создающийся как Internal — это тот самый несоздаваемый объект, про которые мы уже говорили. Он не регистрируется в реестре и не может быть создан непосредственно клиентом. Если объект создаётся как Single Instance, каждый клиент будет работать со своей копией сервера. Это обеспечивается флагом RegCls_SingleUse в параметрах функции CoRegisterClassObject: если этот флаг установлен, то сразу после того как один из клиентов получит указатель на зарегистрированную таким образом фабрику класса, только этот клиент и будет её видеть. Для всех остальных приложений фабрика класса перестанет быть зарегистрированной, поэтому попытка другого клиента использовать такой же COM-объект приведёт к запуску новой копии сервера. И, наконец, выбранный по умолчанию вариант Multiple Instance означает, что фабрика класса будет доступна всем клиентам независимо от того, кто из них первый её использовал. Что касается внутреннего COM-сервера, то он, во-первых, не использует CoRegisterClassObject, а во-вторых, такой сервер в силу внутренней природы DLL не может работать одновременно с несколькими клиентами, поэтому для него разницы между Single Instance и Multiple Instance нет.

Примечание: Не следует путать Single Instance и синглетон. Single Instance означает, что для каждого клиента будет запускаться отдельная копия внешнего сервера. При этом клиент может создавать несколько COM-объектов в этом сервере, и все они могут быть разными. Что касается внешнего сервера, COM-объекты в котором реализованы как синглетоны, то одна копия такого сервера вполне может работать одновременно с несколькими клиентами, но все эти клиенты будут работать с одним и тем же COM-объектом.

Следующее поле — Threading Model (нитевая модель). Это довольно сложное понятие, которому мы в своё время посвятим отдельный урок. Сейчас же отметим, что нитевая модель — это своего рода контракт, заключаемый между COM-объектом и операционной системой по синхронизации в случае распараллеливания работы на несколько нитей. Этот контракт определяет разграничение ответственности — в каких случаях сервер самостоятельно реализует синхронизацию доступа к данным из разных нитей, в каких это за него делает система. Во многих частных случаях сервер может синхронизировать доступ к данным гораздо эффективнее, чем это делает система, использующая универсальные методы, но такая эффективность требуется не всегда, что позволяет упрощать сервер, перекладывая задачу синхронизации на систему. В нашем случае мы пока не предоставляем системе средства, с помощью которых она могла бы выполнить свою часть контракта (именно поэтому клиент должен работать с сервером только из главной нити), так что нам подходит любой из предложенных для этого поля вариантов, кроме Free.

Поле Description содержит простое описание кокласса, которое используется при регистрации в реестре. И, наконец, очень важный момент — галочка Include Type Library должна быть снята. Библиотека типов — это очень мощный инструмент, и мы обязательно познакомимся с ней поближе, но пока наш сервер обойдётся без неё.

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

unit Unit1;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  Windows, ActiveX, Classes, ComObj;

type
  TSimpleServer = class(TComObject)
  protected
  end;

const
  Class_SimpleServer: TGUID = '{8C35EF6B-98C3-4E51-BFD3-388AA168D4CA}';

implementation

uses ComServ;

initialization
  TComObjectFactory.Create(ComServer, TSimpleServer, Class_SimpleServer,
    'SimpleServer', 'Simple Server', ciMultiInstance, tmApartment);
end.

Эксперт автоматически создал заготовку для нашего кокласса, константу, содержащую его CLSID (которая начинается с префикса Class вместо традиционного CLSID), а в раздел инициализации вставил создание фабрики класса (причём уже отсюда видно, что фабрика класса создаётся только один раз при запуске сервера, а не по запросам клиента).

Созданные экспертом модули мы сохраним под именами SimpleServer.dpr и SimpleServerMain.pas соответственно.

Поддержку интерфейса ILowLevelServer придётся добавлять вручную. Для этого, во-первых, добавим модуль LLInterface в раздел uses. Затем переделаем объявление класса TSimpleServer, чтобы оно выглядело так:

TSimpleServer = class(TComObject, ILowLevelServer)
protected
  // Аргументы, переданные через SetParameters
  FArg1, FArg2: Integer;
  // Методы интерфейса ILowLevelServer
  function SetParameters(A, B: Integer): HResult; stdcall;
  function GetSum(out Sum: Integer): HResult; stdcall;
end;

Реализация методов SetParameters и GetSum ничем не отличается от их реализации в "ручном" сервере из предыдущего урока. Можно просто скопировать их текст из модуля LLCoClass.pas.

На этом создание простого сервера средствами Delphi завершено. Его можно регистрировать (с помощью RegSvr32.exe) и подключаться к нему клиентом. На этом простом примере хорошо видно, от скольких проблем избавляют разработчика средства Delphi: фактически, единственное, что нам здесь пришлось программировать — это поддержку специфичного для нашего сервера интерфейса ILowLevelServer, а всё то, что составляет основу любого COM-сервера, уже реализовано в модулях ComObj и ComServ. К сожалению, в дальнейшем мы увидим, что эти модули имеют свои ограничения, и многие стандартные аспекты COM/DCOM всё же приходится реализовывать самостоятельно.

Включённый в примеры к статье клиент для нашего сервера (модули SimpleClient.dpr и ClientMain.pas) практически ничем не отличается от клиента из предыдущего урока. Только при вызове CreateComObject в TClientForm.FormCreate константы CLSID_LowLevelServer и CLSID_LowLevelSingleton заменены на Class_SimpleServer и Class_SimpleSingleton соответственно. Кроме того, пришлось объявить эти константы в самом клиенте, т.к. эксперт Delphi не локализовал их в отдельных модулях, которые можно было бы импортировать в клиенте. Во всём же остальном этот клиент — точная копия предыдущего.

Прежде чем переходить к созданию синглетона, разберёмся с устройством классов TComObject, TComObjectFactory, TComClassManager, TComServerObject и TComServer, которые делают создание COM-серверов таким простым. Разобраться с ними до конца мы, конечно, не сможем — в них предусмотрена поддержка многого из того, что мы ещё не изучали. Строго говоря, мы даже не сможем объяснить, как TComObject реализует базовый интерфейс IUnknown, потому что эта реализация использует делегирование и поддерживает агрегацию, с которыми мы пока не знакомы. Так что на этом уроке мы ограничимся только общими принципами устройства этих классов, а знакомиться с деталями их реализации будем в следующих уроках по мере изучения соответствующих разделов.

Все упомянутые классы, кроме TComServer, реализуются модулем ComObj; TComServer реализуется модулем ComServ. Если коротко, то эти классы делают следующее: TComObject реализует COM-объект. Фабрика класса, реализуемая классом TComObjectFactory, отвечает, во-первых, за создание COM-объектов, а во-вторых, является связующим звеном между созданными ей COM-объектами и объектом сервера. Сам объект сервера реализуется классом TComServer, а TComServerObject — это абстрактный базовый класс для него. TComClassManager просто ведёт список всех созданных в программе фабрик класса. Рассмотрим каждый из этих классов подробнее.

В классе TComObject имеется сразу три конструктора: Create, CreateAggregated и CreateFromFactory. Хотя из названия можно заключить, что только CreateFromFactory использует для создания экземпляра фабрику класса, на самом деле и Create, и CreateAggregated работают через вызов CreateFromFactory. И хотя среди параметров этих двух конструкторов нет указателя на используемую фабрику класса, VCL пытается её найти, используя TComClassManager, и в случае неудачи генерирует исключение, так что создавать экземпляры TComObject, не создав предварительно фабрику класса, нельзя.

Однако между объектами, созданными с помощью CreateFromFactory, и созданными с помощью Create или CreateAggregated, есть существенная разница: первые учитываются при подсчёте созданных фабрикой класса объектов, вторые — не учитываются. Как мы увидим позже, этот учёт нужен для того, чтобы определить, когда сервер может быть выгружен. Следовательно, те COM-объекты, которые создаются для клиента, а не для внутреннего использования сервером, должны создаваться с помощью прямого вызова CreateFromFactory, иначе сервер может быть признан неиспользуемым, даже если такой объект будет использоваться клиентом. Каждый экземпляр TComObject сам отвечает за то, чтобы увеличить счётчик объектов, реализуемый объектом сервера, и уменьшить его при завершении существования.

Примечание: Параметр Controller конструктора — это указатель на управляющий объект, то же самое, что параметр pUnkOuter у функции CoCreateInstance, т.е. указатель на внешний IUnknown. Напоминаем, что смысл этого указателя мы пока не рассматриваем, он у нас пока всегда равен nil.

Класс TComObject реализует IUnknown, однако, как мы уже говорили, пока мы не можем описать все детали этой реализации. Отметим только, что она подходит для подавляющего большинства COM-объектов. Этот класс также реализует интерфейс ISupportErrorInfo — стандартный интерфейс для поддержки т.н. COM-исключений, с которыми мы познакомимся в одном из следующих уроков.

Класс TComObjectFactory реализует фабрику классов с интерфейсами IClassFactory, IClassFactory2 и, конечно же, IUnknown. Реализация IUnknown проста: QueryInterface реализуется стандартным для Delphi способом — через GetInterace, _AddRef и _Release увеличивают и уменьшают счётчик ссылок. Нестандартным здесь является то, что своего счётчика ссылок TComObjectFactory не имеет, используя счётчик объекта сервера, который подсчитывает общее количество ссылок на все фабрики класса, а также то, что _Release никогда не удаляет фабрику класса — она существует до тех пор, пока сервер считает это нужным, т.е., в большинстве случаев, до окончания работы сервера.

Интерфейс IClassFactory2 и его реализацию в классе TComObjectFactory мы рассматривать не будем — это выходит за рамки нашего курса. Рассмотрим только реализацию IClassFactory. Метод CreateInstance переадресовывает вызов более универсальному IClassFactory2.CreateInstanceLic, пригодному для создания COM-объектов как в тех случаях, когда лицензия требуется, так и в тех, когда она не требуется. Эта функция выполняет техническую работу по проверке корректности параметров, требований лицензии и по обработке исключений, а собственно для создания экземпляра TComObject вызывает виртуальный метод CreateComObject, который создаёт объект с помощью конструктора CreateFromFactory. Благодаря виртуальности этого метода наследники TComObjectFactory могут менять политику создания COM-объектов.

Метод LockServer, во-первых, вызывает системную функцию CoLockServerExternal, которая создаёт или освобождает системную ссылку на эту фабрику, т.е. при установки блокировки система вызывает _AddRef фабрики класса, при снятии — вызывает _Release. Как мы уже сказали, это приводит к изменению счётчика фабрик класса, который ведёт COM-объект сервера. Затем метод LockServer изменяет счётчик COM-объектов, который также ведётся объектом сервера. Таким образом, при вызове LockServer с параметром True увеличиваются сразу оба этих счётчика, при вызове с параметром False — оба уменьшаются. Сложно сказать, зачем разработчикам VCL понадобилось такое дублирование, но, по крайней мере, ни к каким ошибкам в работе оно не приводит.

Объект TComObjectFactory хранит в своих свойствах параметры связанного с фабрикой кокласса: CLSID (свойство ClassID), ProgID (свойство ProgID), описание, которое помещается в реестр при регистрации (свойство Description), нитевую модель, способ создания (instancing), указатель на класс, реализующий кокласс. Единственный конструктор класса TComObjectFactory позволяет установить эти параметры. Через него также задаётся объект сервера, используемый программой.

Из методов TComObjectFactory следует также отметить RegisterClassObject и UpdateRegistry. Первый из них регистрирует фабрику класса в системе с помощью CoRegisterClassObject (актуально только для внешних серверов). Второй из этих методов, UpdateRegistry, вносит изменения в реестр — добавляет туда информацию о своём коклассе или удаляет её в зависимости от значения своего параметра типа Boolean.

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

Список фабрик класса реализуется классом TComObjectManager. Этот класс выполняет чисто техническую работу: инкапсулирует список фабрик класса, позволяет искать в нём требуемую фабрику класса и выполнять групповые операции над всеми фабриками класса из списка. Экземпляр TComObjectManager создаётся в единственном числе, для доступа к нему используется функция ComClassManager.

Выше мы уже не раз упоминали объект сервера. Это объект, который реализует всё то, что должен делать COM-сервер: ведёт подсчёт ссылок, обеспечивает регистрацию своих COM-объектов реестре, выгружает неиспользуемый сервер и т.п. В модуле ComObj, в котором описаны все рассмотренные выше классы, описан также TComServerObject — абстрактный интерфейс для класса, реализующего сервер. Здесь слово "интерфейс" понимается не как интерфейсный тип, а в более широком смысле — как описание способа взаимодействия без реализации. TComServerObject — это класс, содержащий все необходимые для объекта сервера методы, но эти методы абстрактные. Каждому экземпляру класса TComObjectFactory в конструкторе передаётся ссылка на объект, являющийся наследником TComServerObject и реализующий все его абстрактные методы — без этого фабрика класса будет неработоспособна. Соответственно, где-то должен быть реализован такой наследник и создан его экземпляр.

В VCL этот наследник реализуется классом TComServer из модуля ComServ. Функциональность этого класса такова, что он подходит для реализации как внутреннего, так и внешнего сервера. Его свойство IsInprocServer типа Boolean служит переключателем между этими типами серверов. Экземпляр TComServer создаётся автоматически в секции инициализации модуля ComServ, указатель на этот объект помещается в экспортируемую модулем переменную ComServer. Если мы вернёмся к коду модуля SimpleServerMain, то увидим, что ComServer — это первый параметр при вызове конструктора класса TComObjectFactory — именно так фабрика класса получает ссылку на объект сервера.

Примечание: Так как модуль ComObj нигде не содержит ссылок на ComServ, а к объекту сервера доступ идёт через абстрактный TComServerObject, возникает мысль о том, что объект сервера может быть реализован и каким-то другим наследником TComServerObject, не обязательно TComServer. Такая возможность действительно существует, однако разработчики VCL нигде её не используют — TComServer является единственным наследником TComServerObject. Более того, TComServer достаточно полно реализует всё, что должен делать объект сервера, поэтому мы не можем назвать ситуацию, когда написание своего наследника TComServerObject могло бы быть полезным.

Как мы уже говорили выше, объект сервера реализует два счётчика: счётчик COM-объектов и счётчик ссылок на фабрики класса, доступ к которым производится через функции ObjectCount и FactoryCount. Эти функции имеют один параметр типа Boolean и в зависимости от его значения увеличивают или уменьшают соответствующий счётчик. Для внутреннего сервера обе эти функции просто увеличивают или уменьшают значение соответствующего поля — т.к. внутренний сервер сам себя никогда не выгружает, выполнять дополнительные действия при обнулении этого счётчика такому серверу нет нужды.

Объект сервера умеет регистрировать все свои COM-объекты в реестре и удалять из реестра информацию о них. Для этого он, пользуясь объектом ComClassManager, вызывает у всех своих фабрик класса метод UpdateRegistry с соответствующим параметром. Фабрика класса не может полностью самостоятельно зарегистрировать свой кокласс в реестре — объект TComObjectFactory не знает ни имя файла сервера, ни его тип (внутренний/внешний) и поэтому не может определить имя ключа, под которым записывается в реестр путь к серверу, а также сам этот путь. Имя ключа содержит свойство ServerKey объекта сервера, имя файла — свойство ServerFileName.

Кроме того, ProgID своего кокласса фабрика класса формирует также с помощью объекта сервера, точнее, его свойства ServerName. ProgID формируется из значения ServerName и имени кокласса, разделённых точками. Свойство ServerName доступно только для чтения, но имя сервера можно задать явно с помощью метода SetServerName. Если этого не сделано, имя сервера будет взято из библиотеки типов (если она есть) или будет совпадать с именем DLL— или EXE-файла сервера (без пути и расширения), как в нашем случае.

Мы видим, что значение ProgID, формируемое Delphi, не полностью соответствует шаблону, рекомендуемому в COM: в нём отсутствует номер версии. Изменить алгоритм формирования ProgID очень просто: достаточно породить наследника от TComObjectFactory и перекрыть в нём виртуальный метод GetProgID. Но если вы хотите, чтобы ваш кокласс полностью соответствовал рекомендациям COM, вам придётся также перекрыть метод UpdateRegistry (который, к счастью, тоже виртуальный), потому что стандартная реализация этого метода вообще не учитывает существование параметра VersionIndependentProgID. К счастью, в большинстве случаев нужды столь точно следовать рекомендациям COM необходимости нет — сервер и так будет вполне работоспособен.

Класс TComServer содержит также методы для реализации внешних COM-серверов и для поддержки библиотеки типов. С этими частями объекта сервера мы будем знакомиться по мере изучения соответствующих разделов.

Кроме класса TComServer, модуль ComServ экспортирует четыре уже знакомых нам функции: DllGetClassObject, DllCanUnloadNow, DllRegisterServer и DllUnregisterServer. Их реализация очень проста, потому что большую часть работы делают объект сервера и список фабрик класса. Функция DllGetClassObject ищет в списке требуемую фабрику класса и возвращает указатель на её интерфейс. Функция DllCanUnloadNow проверяет на равенство нулю счётчик COM-объектов и счётчик ссылок на фабрики класса. DllRegisterServer и DllUnregisterServer просто вызывают метод UpdateRegistry с соответствующим параметром.

Вот мы и познакомились с теми средствами, которые делают создание COM-серверов в Delphi таким лёгким делом. Но эти средства предусматривают не все возможные ситуации, поэтому иногда приходится вмешиваться в их работу (к счастью, не очень часто и не очень кардинально). Если вы внимательно читали то, что написано про TComObject и TComObjectFactory, то, возможно, обратили внимание, что мы нигде там не упоминали синглетон. Действительно, в VCL нет средств для реализации синглетона. Тем не менее, их легко добавить.

Чтобы создать синглетон, сначала проделаем все те же действия, что и при создании обычного сервера: создадим новый проект по шаблону ActiveX Library, добавим в него COM-объект и реализуем в нём поддержку интерфейса ILowLevelServer. В результате мы должны получить проект, который отличается от предыдущего проекта SimpleServer только названием класса, CLSID и названием файла. В прилагаемых к уроку примерах файлы проекта имеют имена SimpleSingleton.dpr и SingletonMain.pas, имя класса COM-объекта — TSimpleSingleton.

Теперь объявим глобальную переменную SingletonInstance типа TSimpleSingleton, которая будет указывать на единственный экземпляр нашего COM-объекта. Самому классу TSimpleSingleton добавим деструктор, который будет очищать эту переменную при уничтожении объекта:

destructor TSimpleSingleton.Destroy;
begin
  SingletonInstance := nil;
  inherited;
end;

На этом модификация класса TSimpleSingleton закончена. Теперь нужно реализовать для него фабрику класса, которая, разумеется, будет унаследована от TComObjectFactory. Мы помним, что непосредственно за создание COM-объекта отвечает метод CreateComObject. Его мы и перекроем. В итоге код фабрики класса будет выглядеть так:

type
  TSingletonFactory = class(TComObjectFactory)
  public
    function CreateComObject(const Controller: IUnknown): TComObject; override;
  end;

function TSingletonFactory.CreateComObject(const Controller: IUnknown): TComObject;
begin
  if SingletonInstance = nil then
    SingletonInstance := TSimpleSingleton.CreateFromFactory(Self, Controller);
  Result := SingletonInstance;
end;

Единственное, что нам осталось — это в разделе initialization изменить тип создаваемой фабрики класса с TComClassFactory на TSingletonFactory. Всё, синглетон готов. В том, что это действительно синглетон, легко убедиться с помощью клиента.

Мы уже упоминали, что технология COM не привязана к конкретному языку, и сервер и клиент могут быть написаны на разных языках. Для доказательства того, что наши серверы действительно COM-серверы, напишем клиент на Visual C++, который сможет работать со всеми четырьмя серверами: LowLevelServer, LowLevelSingleton, SimpleServer и SimpleSingleton. Этот пример находится в прилагаемом к уроку архиве в папке CppClient. Здесь мы его не будем разбирать, но рекомендуем всё же ознакомиться с его кодом.

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


предыдущий урок содержание семинара следующий урок



К материалу прилагаются файлы:


Смотрите также материалы по темам:


 Обсуждение материала [ 03-01-2010 22:32 ] 18 сообщений
  
Время на сайте: GMT минус 5 часов

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

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования