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


  • Текст добавлен: 10 ноября 2013, 00:08


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


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


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

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

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

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

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

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


function CreateFileMapping(hFile: THandle;

lpFileMappingAttributes: PSecurityAttributes;

flProtect, dwMaximumSizeHigh, dwMaximumSizeLow: DWORD;

lpName: PChar): THandle;


Параметры этой функции имеют следующие значения.

• hFile – идентификатор файла. Посредством присвоения этому аргументу значения константы INVALI D_HAN D L E_VALU E создаваемый объект файлового отображения связывается со страничным swap-файлом (системным файлом подкачки).

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

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

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

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

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

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

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


function MapViewOfFile (hFileMappingObject: THandle;

dwDesiredAccess: DWORD;

dwFileOffsetHigh, dwFileOffsetLow,

dwNumberOfBytesToMap: DWORD): Pointer;


Значения параметров этой функции следующие.

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

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

• dwFileOffsetHigh, dwFileOffsetLow – 64-битное смещение от начала файла.

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

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

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


function UnMapViewOfFile(lpBaseAddress: Pointer): Boolean;


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

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


function CloseHandle(hFileMapObj: THandle):Boolean;


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

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

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

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


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


var

Form MappingFile: TFormMappingFile;

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

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

hFileMapObj:THandle;

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

lpBaseAddress:PChar;


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

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

procedureTMappingFile.FormCreate (Sender: TObject);

begin

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

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

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

hFileMapObjj:=

CreateFileMapping(MAXDWORD,Nil,PAGE_READWRITE,0,4,'FileMemory');

Ifffff(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 при рассмотрении программы «Оконный шпион», в которой проекция файла в память используется для передачи данных из функции DLL, работающей в памяти другого процесса.

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

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

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

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

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


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

Технология 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, поэтому специально для нее была создана технология взаимодействия между программами – COM (Component Object Model, «модель компонентных объектов»), которая оказалась весьма удачной. Начиная с Windows 95, DDE была объявлена устаревшей, а основной технологией обмена данными в системе стала технология COM.

Технология COM

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

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

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

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

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

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

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

Использование технологии OLE в Delphi

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

Microsoft Office с точки зрения COM

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

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

Объект Application

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

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

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

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

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

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

Фактически в системе произошло следующее. В реестре был найден зарегистрированный ранее COM-сервер приложения Microsoft Word. Используя все тот же реестр, был найден путь к программе и произведен ее запуск. Вследствие этого в списке процессов появился процесс, отвечающий за работу редактора Microsoft Word.

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

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

procedureTFormStartWord.ButtonStartClick(Sender: TObject);

var

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

Wordvarrr: OleVariant;

file_Nameee: 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 – модуль работы с объектами COM. Важно отметить, что наличие функций, вызываемых для объекта wo rdva r, определяется в период выполнения. Это значит, что ошибка может обнаружиться только в период выполнения программы, поэтому весь код работы с объектом помещен в блок try.

Класс TOLEServer

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

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

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

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

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

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

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

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

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

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

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

Поскольку компоненты использоваться не будут, в секцию uses следует добавить модуль ComObj. Для работы с COM-сервером редактора понадобится объект OLE. Переменную типа OleVariant можно добавить следующим образом:


var

//Объект OLE

Wrd: OleVariant;


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

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

procedureTFormWord.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.Selecti on.TypeText('Технология COM является одной из современных');

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

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

Wrd.Selection.Font.Size:= 12;

Wrd.Selection.Font.Bold:= false;

Wrd.Selection.Font.Italic:= true;

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

Wrd.Selection.Font.Bold:= true;

Wrd.Selecti on.TypeText('Delphi'#13#10#13#10);

end;


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

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

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

procedureTFormWord.bnAddTableClick(Sender: TObject);

begin

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

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

end;


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

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

procedureTFormWord.bnSaveClick(Sender: TObject);

begin

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

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

'_result.DOC');

end;


Сохранение осуществляется путем вызова метода SaveAs объекта 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 и WordApplication. Далее укажите свойству ConnectKind компонента WordApplication значение ckRunningInstance, а также свойству AutoConnect значение True. В данном случае приложение Microsoft Word создаваться не будет, а программа подключится к уже существующему серверу. Основную практическую ценность здесь представляет механизм определения активного документа и добавление в него текста, даты и времени (листинг 9.7).

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

procedure TFormActiveWord.WordApplicationActiveDocumentChange(Sender: TObject);

begin

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

WordDocumentNew.ConnectTo(WordApplicationActive.ActiveDocument);

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

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

WordApplicationActi ve.Acti veDocument .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'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.


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


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