Электронная библиотека » Александр Чиртик » » онлайн чтение - страница 17

Текст книги "Delphi. Трюки и эффекты"


  • Текст добавлен: 22 января 2014, 03:26


Автор книги: Александр Чиртик


Жанр: Программирование, Компьютеры


сообщить о неприемлемом содержимом

Текущая страница: 17 (всего у книги 24 страниц)

Шрифт:
- 100% +
8.3. Проецируемые в память файлы

Не менее мощным и гибким методом организации обмена данными между приложениями является метод, который базируется на проецируемых в память файлах (Files Mapping). Главная идея этого механизма основывается на использовании динамической разделяемой памяти системы для хранения в ней данных. Как известно, каждый процесс имеет свой участок памяти, называемый виртуальным адресным пространством. При использовании механизма проецируемых в память файлов данные становятся доступны из любого процесса, который использует этот файл. В этом случае говорят, что файл отображается в виртуальное адресное пространство процесса, поэтому данные, хранимые в файле, доступны процессу, который этот файл открыл. Механизм проецирования файлов в память используется, например, для исполняемых файлов приложений, а также для DLL.

Для работы с проецируемыми в память файлами существует целый ряд API-функций. Но прежде чем их рассматривать, разберемся в процессе организации обмена данными через проецируемые файлы. На первом этапе необходимо создать объект (файл, отображаемый в память), затем «отобразить» созданный объект в адресное пространство процесса приложения, получая возможность записи и чтения данных их этого файла. При отображении файла на определенный участок памяти (адресного пространства процесса) манипуляции с данными этого участка памяти отражаются на содержимом файла. После произведенных над объектом манипуляций необходимо закрыть доступ к данным файла (удалить проекцию и закрыть файл).

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

function CreateFileMapping(hFile: THandle;

lpFileMappingAttributes: PSecurityAttributes;

flProtect, dwMaximumSizeHigh, dwMaximumSizeLow: DWORD;

lpName: PChar ): THandle;

Подробнее рассмотрим параметры функции.

• hFile – идентификатор файла. В результате присвоения этому аргументу значения константы INVALID_HANDLE_VALUE мы свяжем создаваемыйобъект файлового отображения со страничным swap-файлом (системным файлом подкачки).

• lpFileMappingAttributes – указатель на структуру типа TSecurity-Attributes. Структура содержит параметры безопасности создаваемого файла.

• flProtect – параметр, задающий способ совместного использования создаваемого объекта, в случае доступа на чтение и запись принимает значение PAGE_ READWRITE.

• dwMaximumSi zeHigh – старший разряд 64-битного значения размера выделяемого объема памяти.

• dwMaximumSizeLow – младший разряд 64-битного значения размера выделяемого объема памяти.

• lpName – имя объекта проецируемого файла (может быть nil для создания безымянной проекции файла).

Функция возвращает глобальный дескриптор (THandle). Если проецируемый файл не создан, то функция CreateFileMapping возвращает нулевое значение.

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

function MapViewOfFile(hFileMappingObject: THandle;

dwDesiredAccess: DWORD;

dwFileOffsetHigh, dwFileOffsetLow,

dwNumberOfBytesToMap: DWORD ): Pointer;

Функция имеет следующие параметры.

• hFileMappingOb j ect – описатель созданного объекта файлового отображения.

• dwDesiredAccess – параметр доступа к полученным данным, в случае чтения и записи принимает значение FILE_MAP_WRITE.

• dwFileOf f setHigh, dwFileOf fsetLow – 64-битное смещение от начала файла.

• dwNumberOf BytesToMap – указывает, сколько байт будет отображено. Если этот аргумент имеет значение 0, то на область памяти будет отображен весь файл.

В результате успешного выполнения функции MapViewOfFile будет получен указатель (тип Pointer) на начальный адрес данных объекта. Указатель будет использоваться в дальнейшем для записи или чтения файла.

Следующей функцией, противоположной по производимым действиям функции MapViewOfFile, является UnMapViewOf File. Она отключает проецируемый файл от текущего процесса:

function UnMapViewOfFile(lpBaseAddress: Pointer): Boolean;

Функция принимает указатель, возвращаемый MapViewOfFile, и использует его для отмены проекции файла на адресное пространство процесса. В случае успешной выгрузки функция возвращает True, в противном случае – False.

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

function CloseHandle(hFileMapObj: THandle):Boolean;

Как видно из синтаксиса функции, она принимает описатель объекта файлового отображения, полученный в результате выполнения функции CreateFileMapping и освобождает его. Для правильного завершения работы с объектом файлового отображения сначала следует применить функцию UnMapViewOf File, а затем CloseHandle.

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

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

Рис. 8.2. Вид приложения, использующего проецируемый файл


В секцию описания переменных программы помещаем следующие объявления:

var

FormMappingFile: TFormMappingFile;

//Глобальные переменные

//Описатель объекта проецируемого файла

hFileMapObj:THandle;

//Указатель на начальный адрес данных

lpBaseAddress:PChar;

Далее рассмотрим, какие действия выполняются при загрузке формы. Создание проецируемого файла и его отображение в адресное пространство процесса выполняется в момент создания формы (листинг 8.7).

Листинг 8.7. Создание формы приложения

procedure TMappingFile.FormCreate(Sender: TObject);

begin

//Создаем проецируемый файл с именем FileMemory

//и передаем полученный в результате описатель

//в глобальную переменную hFileMapObj

hFileMapObj := CreateFileMapping(MAXDWORD,Nil,PAGE_READWRITE,

0,4,’FileMemory’);

If (hFileMapObj = 0) Then

ShowMessage('Не могу создать проецируемый файл!')

Else

//Подключаем файл к адресному пространству

//и получаем начальный адрес данных

lpBaseAddress := MapViewOfFile(hFileMapObj,FILE_MAP_WRITE,

0,0,0);

If lpBaseAddress = Nil Then

ShowMessage('Не могу подключить проецируемый файл!');

end;

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

Листинг 8.8. Копирование данных в проецируемый файл

procedure TMappingFile.bnOKClick(Sender: TObject);

begin

//Считываем данные в проецируемый файл

StrPCopy(lpBaseAddress,edVariable.Text);

end;

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

Листинг 8.9. Считывание данных из проекции файла

procedure TMappingFile.TimerMFTimer(Sender: TObject);

begin

lbVariable.Caption := PChar(lpBaseAddress);

end;

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

Листинг 8.10. Уничтожение формы приложения

procedure TMappingFile.FormClose(Sender: TObject; var Action:

TCloseAction);

begin

//Отключим файл от адресного пространства

UnMapViewOfFile(lpBaseAddress);

//Освобождаем объект файла

CloseHandle(hFileMapObj);

//Закрываем форму

Action := caFree;

end;

Здесь рассмотрен простой пример работы с проекцией файла в рамках одного приложения. Более же интересный и реальный пример вы увидите в разд. 10.2 при рассмотрении программы «Оконный шпион»: там проекция файла в память используется для передачи данных из функции DLL, работающей в памяти другого процесса.

Глава 9
Возможности COM в Microsoft Word и Microsoft Excel

• Технология OLE

• Технология COM

• Использование OLE в Delphi

• Управление Microsoft Word и Microsoft Excel


Технология COM/DCOM является одной из важных и широко используемых современных технологий. Охватить все аспекты технологии COM/DCOM очень сложно, и в рамках данной книги в этом нет необходимости. В этой главе будут рассмотрены основные возможности СОМ и их практическое применение. Примеры, разобранные в главе, демонстрируют управление приложениями, снабжаемыми СОМ-объектами. К таким приложениям можно отнести все программы из пакета Microsoft Office (Microsoft Word, Microsoft Excel и т. д.).

9.1. Технология OLE

В Windows 3.1 и более ранних версиях основным средством обмена данными между программами была технология DDE – Dynamic Data Exchange (динамический обмен данными). На этой технологии основывалась технология OLE – Object Linking and Embedding (связывание и внедрение объектов). OLE позволяет делать документы одного приложения частью документов другого приложения. Таким образом, пользователь получил возможность применять функции различных программ для редактирования одного документа.

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

Начиная с Windows NT 3.51, внедряется технология OLE 2 – дальнейшее развитие OLE. OLE 2 дополнительно включает в себя технологию ActiveX. Позже термин OLE 2 изменили на OLE.

Технология DDE была недостаточной для поддержки OLE 2, поэтому специально для нее была создана технология взаимодействия между программами – СОМ (Component Object Model, модель компонентных объектов). СОМ оказалась очень удачной технологией, поэтому, начиная с Windows 95, DDE была объявлена устаревшей, а основной технологией обмена данными в системе стала технология СОМ.

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

Модель СОМ построена по принципу архитектуры «клиент – сервер». Сервер предоставляет список возможных действий (функций), которые могут использоваться клиентским процессом. Таким образом, серверный процесс позволяет обрабатывать запросы клиента, выполняя некоторые действия. Когда взаимодействие между клиентом и сервером подразумевает обмен данными, эти данные передаются в качестве параметров функций. При необходимости клиент также может экспортировать функции, которые могут быть вызваны сервером.

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

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

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

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

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

На данный момент DCOM является межплатформенной технологией. Существуют средства для поддержки DCOM в различных UNIX-системах (в том числе Linux), Solaris, MacOS, VxWorks.

9.3. Использование OLE в Delphi

Как и многие современные среды программирования, Delphi поддерживает возможность автоматизированной разработки приложений, работающих с различными СОМ-сервисами или серверами. Для более глубокого понимания принципов работы приложений, создаваемых на основе СОМ-технологии, проведем краткий обзор наиболее доступных СОМ-серверов – приложений пакета Microsoft Office.

Microsoft Office с точки зрения СОМ

Microsoft Office является средой, в которой большая часть задач решается без использования программирования. Но ценность приложений Microsoft Office за – ключается в том, что все задачи решаются как традиционным способом (ручное редактирование), так и посредством применения программирования на известном языке VBA (Visual Basic for Application). Кроме того, приложения пакета снабжаются серверами СОМ, которые предоставляют интерфейс доступа к приложению и его объектам. Благодаря этому разработчик в среде Delphi имеет возможность, создав контроллер автоматизации, управлять сервером. Приложение Microsoft Office можно рассматривать как совокупность объектов с их методами и свойствами (они организуют основу программы). Как правило, в каждом приложении существует так называемый корневой объект, который носит название Application. Каждое приложение Microsoft Office имеет собственный корневой обект – Word. Application, Excel.Application. Приложение само является корневым объектом, несмотря на это, в объект Application встраиваются все остальные объекты (участники), которые являются свойствами главного объекта. Документ, созданный на базе СОМ, предоставляет большое количество разнообразных методов, но имеются и одинаковые методы в различных приложениях Microsoft Office. Например, Run, Quit, Activate.

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

Объект Application

Безусловно, самым важным объектом в приложениях Microsoft Office является объект Application. Небольшой пример использования данного объекта продемонстрирует простоту программирования с применением СОМ. Для решения часто встречающихся задач используются уже хорошо нам известные компоненты среды Delphi. В случае технологии СОМ это не является исключением. Запустим сервер приложения Microsoft Word. Для этого выполним следующее.

1. Создаем новый проект.

2. На главную форму приложения помещаем компонент WordApplication вкладки Servers.

3. Задаем свойства компонента AutoConnect и AutoQuit значением True.

4. Запускаем созданное приложение.

На первый взгляд, ничего существенного не происходит, но результат работы программы можно заметить путем просмотра запущенных процессов (не путать с задачами). В приложении Диспетчер задач среди процессов различных приложений можно увидеть WordCOM. ехе. Этот факт говорит нам о том, что созданное нами посредством СОМ приложение подключилось к серверу Microsoft Word и запустило его. Фактически в системе произошло следующее. В реестре был найден зарегистрированный ранее СОМ-сервер приложения Microsoft Word. Используя все тот же реестр, был найден путь к программе и произведен ее запуск. Вследствие этого мы заметили появление процесса, отвечающего за работу редактора Microsoft Word.

Но для того чтобы лучше понять запуск приложения Microsoft Word, приведем фрагмент исходного текста, результат работы которого аналогичен (листинг 9.1).

Листинг 9.1. Запуск Microsoft Word

procedure TFormStartWord.ButtonStartClick(Sender: TObject);

var

//Переменная, интерфейс к объекту

Wordvar : OleVariant;

file_Name : string;

begin

//Начало блока перехвата исключения

try

file_Name := ExtractFilePath(Application.EXEName) +

'worddoc.DOC';

//Инициализируем объект интерфейса

//для доступа к серверу COM Microsoft Word

Wordvar := CreateOleObject('Word.Application');

//Добавление документа

wordvar.application.documents.add;

wordvar.application.activedocument.range.insertAfter(now);

//Сохранение документа (аналог действиям: «Сохранить как…»,

//с указанием имени файла)

wordvar.application.activedocument.saveas(fileName);

//Завершение работы с приложением и выгрузка COM-сервера

wordvar.application.quit(true,0);

end;

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

Класс TOLEServer

На вкладке Servers находится набор компонентов для доступа к серверам автоматизации. Не все компоненты возвращают ссылку на объект Application, то есть могут быть получены интерфейсы для доступа к таким вложенным объектам, как документ Microsoft Word или рабочая книга Microsoft Excel. Все компоненты унаследованы от класса TOLEServer, который наследует свойства класса Tcomponent. TOLEServer является базовым классом всех СОМ-серверов. Кроме этого, данный класс имеет еще несколько свойств и методов для управления связью с СОМ-сервером. Среди таковых уже знакомое нам свойство AutoConnect, которое автоматически запускает СОМ-сервер и извлекает из него интерфейс, обеспечивающий связь с контроллером. Еще одно важное свойство класса TOLEServer – это ConnectKind, указывающее тип процесса, с которым устанавливается связь. Свойство используется методом Connect, который вызывается автоматически, если свойство AutoConnect истинно. В табл. 9.1 описаны значения, которые может принимать ConnectKind.

Таблица 9.1. Значение свойства ConnectKind

Более подробно следует рассмотреть значение свойства ConnectKind, равное ckAttachToInterface. Соединение с сервером производится посредством использования главного интерфейса Application, но, например, возникает необходимость подключить к нашему проекту такие компоненты как WordDocument или WordParagraphFormat. В этом случае мы просто подключаемся к уже существующему интерфейсу, а не создаем его заново. Также это может быть необходимо, когда контроллер должен отслеживать события, происходящие в СОМ-сервере.

9.4. Управление Microsoft Word и Microsoft Excel
Трюки в Microsoft Word

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

Рассмотрим пример приложения, которое будет создавать новый документ Microsoft Word, записывать в него некоторый текст, добавлять таблицу и сохранять полученный документ в файл. В целях наилучшего понимания принципов использования объектов СОМ первый пример не будет использовать компонент среды разработки. Итак, приступим к созданию приложения. Для начала создаем новый проект и помещаем на форму следующие кнопки:

• открытия приложения Microsoft Word;

• вывода текста;

• добавления таблицы;

• сохранения документа;

• завершения работы Microsoft Word.

Мы не будем использовать компоненты, поэтому добавляем в секцию uses модуль ComOb j. Для работы с СОМ-сервером редактора нам понадобится объект OLE. Добавляем переменную типа OleVariant:

var

//Объект OLE

Wrd: OleVariant;

Обработчик кнопки запуска редактора имеет следующий вид (листинг 9.2).

Листинг 9.2. Запуск редактора Microsoft Word

procedure TFormWord.bnOpenWordClick(Sender: TObject);

begin

//Создаем объект

Wrd := CreateOleObject('Word.Application');

//Делаем видимым приложение

Wrd.Visible := true;

//Добавляем новый документ

Wrd.Documents.Add;

end;

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

Листинг 9.3. Вывод текста в Microsoft Word

procedure TFormWord.bnSetTextClick(Sender: TObject);

begin

//Процедура записи текста

//Устанавливаем шрифт

Wrd.Selection.Font.Size := 20;

Wrd.Selection.Font.Bold := true;

//Пишем текст

Wrd.Selection.TypeText('Технология COM является одной из современных');

Wrd.Selection.TypeText('технологий организации межпроцессного взаимодействия'#13#10#13#10);

//Задаем новые параметры шрифта

Wrd.Selection.Font.Size := 12;

Wrd.Selection.Font.Bold := false;

Wrd.Selection.Font.Italic := true;

Wrd.Selection.TypeText('Подпись: ');

Wrd.Selection.Font.Bold := true;

Wrd.Selection.TypeText('Delphi'#13#10#13#10);

end;

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

Процедура добавления таблицы является достаточно простой и выглядит следующим образом (листинг 9.4).

Листинг 9.4. Добавление таблицы

procedure TFormWord.bnAddTableClick(Sender: TObject);

begin

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

Wrd.ActiveDocument.Tables.Add(Wrd.Selection.Range,3,3);

end;

Таблица содержит три столбца и столько же строк. Далее следует пояснить обработчик нажатия кнопки сохранения документа (листинг 9.5).

Листинг 9.5. Сохранение документа Microsoft Word

procedure TFormWord.bnSaveClick(Sender: TObject);

begin

//Сохранение документа

Wrd.ActiveDocument.SaveAs(ExtractFilePath(Application.EXEName) +

'_result.DOC');

end;

Сохранение осуществляется путем вызова MeTOflaSaveAs объекта ActiveDocument, который в качестве параметра принимает путь к файлу. После нажатия кнопки сохранения документ с текстом будет записан в файл (result. doc) каталога, из которого была запущена программа.

Процедура завершения работы основана на вызове метода Quit (листинг 9.6).

Листинг 9.6. Завершение работы с Microsoft Word

procedure TFormWord.bnExitWordClick(Sender: TObject);

begin

//Завершение приложения

Wrd.Quit;

end;

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

Далее приступим к созданию приложения, которое будет подключаться к серверу COM Microsoft Word и выводить текст, дату и время вывода этого текста в активный документ при его смене (переключении между документами). На этот раз мы воспользуемся компонентами WordDocument и WordApplication с вкладки Servers.

Создаем новый проект и на главную форму приложения помещаем компоненты WordDocument nWordApplication. Далее устанавливаем CBoftcTBoConnectKind компонента Wo г dApp licationBc kRunni ng I ns t anc e, а также значение свойства AutoConnect в True. В данном случае приложение Microsoft Word создаваться не будет, а программа подключится к уже существующему серверу. Основную практическую ценность для нас представляет механизм определения активного документа и добавление в него текста, даты и времени (листинг 9.7).

Листинг 9.7. Реакция на смену активного документа

procedure TFormActiveWord.WordApplicationActiveDocumentChange

(Sender: TObject);

begin

//Подключаемся к текущему документу

WordDocumentNew.ConnectTo( WordApplicationActive.ActiveDocument);

//Контроллер добавляет новую строку в текущий документ

WordDocumentNew.Range.InsertAfter(#13#10+'Переход к документу'+#13#10+

WordApplicationActive.ActiveDocument.Get_FullName+' произведен :'+ DateTimeToStr(Now));

end;

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

Чтобы просмотреть работу этого приложения, запустите Microsoft Word и создайте в нем два документа. Запустите созданный пример и поочередно активизируйте документы (щелчком кнопкой мыши на Панели задач).


Страницы книги >> Предыдущая | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | Следующая
  • 0 Оценок: 0

Правообладателям!

Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.


Популярные книги за неделю


Рекомендации