Журнал LinuxFormat - перейти на главную

LXF90:Mono

Материал из Linuxformat
Перейти к: навигация, поиск

Mono: Связь с библиотеками

Создать супер-поиск по файловой системе меньше чем за час? Наш штатный вождь обезьян Пол Хадсон делает невозможное не только возможным, но и простым...

Содержание

Ваша миссия, если вы за нее беретесь, состоит в написании консольной программы, которая индексирует файловую систему пользователя в фоновом режиме, сканирует почту, RSS-ленты, историю браузера и содержимое файлов, а затем позволяет производить во всем этом молниеносный поиск. Уловили? ОК, увидимся через час – удачи вам!

Да, мы собрались создать шедевр менее чем за 60 минут; по-вашему, без чуда не обойтись? Но прикиньте: какое небезызвестное Linux-приложение умеет осуществлять поиск по всем источникам данных? Думайте, думайте! Ясное дело, Beagle. А на какой платформе построен Beagle? Mono! А о чем этот учебник? О Mono! Итак, мы позаимствуем возможности у проекта Beagle – точнее, у его библиотек.

Что хорошо в .NET – это шанс встать на плечи гигантам, использовав их библиотеки. Конечно, С тоже такое допускает – например, программа Ark RPG имеет библиотеку libarkrpg0c2a, позволяющую другим программам на С вызывать ее функции, а не реализовывать их заново. Но такой подход не лишен проблем: например, Gstreamer разделен на несколько библиотек, и у каждой собственный номер версии. Рядовой пример – libgstdataprotocol-0.10.so.0.8.1. C не воспринимает новые версии библиотек без обновления самой программы, поэтому номера версий так важны – приложению подавай непременно libgstdataprotocol-0.10, а не 0.9 или 0.11.

Mono позволяет программистам просто потребовать «давай Beagle» и получить любую доступную версию. Такой подход не всегда срабатывает, особенно с некоторыми новыми Linux-приложениями, не достигшими версии 1.0. Причина, по которой у них такие маленькие номера версий (Beagle находится на стадии 0.2.14) – недостаточно стабильный программный интерфейс (application programming interface, API). API – это термин для обозначения имен функций, типов параметров и возвращаемых значений для данной библиотеки. Стабильность API означает, что если функция DoFoo() в версии 1.0 принимает целый аргумент и возвращает строку, то любая использующая ее программа будет работать и с версией библиотеки 1.01, потому что функция не меняется.

Самое главное в API – это 'I'-часть, собственно интерфейс (имя, параметры, возвращаемое значение): если он фиксирован, то неважно, как библиотека выполняет свою задачу, и пусть себе её версии меняются как угодно.

Резюмирую:

  • Мы используем Mono, и Beagle тоже – значит, нам доступны преимущества Beagle.
  • Beagle имеет API, предоставляющий в наше распоряжение его функциональность, но он не стабилен и в будущем может измениться (в прошлом он менялся довольно круто).
  • Чтобы использовать Beagle, достаточно знать имена его функций и как к ним обратиться.

Вам это все еще кажется трудным? А так оно и есть. Но я ведь обещал научить вас всем навыкам, необходимым, чтобы к концу нашей серии уроков вы стали профессионалом, поэтому перейдем к делу...

Начнем с имени нашего приложения. Приложениям Gnome на основе Mono уже приелись имена с буквой G, и я решил выбрать что-нибудь смешное и продвинутое. Предлагаю Poochy, это имя малоизвестного песьеголового персонажа игры Nintendo (даже Майк про него не слыхал!) – оно вполне подходит к собачьей теме, заданной Beagle [англ. «гончий пес»].

Итак, запустите MonoDevelop, выберите File > New Project, затем C# > Console Project, уберите галочку с Create Separate Solution Subdirectory, затем введите имя 'Poochy' в поле Name и нажмите кнопку New внизу. Вы получите старое доброе приложение Hello World – мы его 'программировали' в начале серии уроков; удалите строчки Console.WriteLine() и оставьте метод Main() пустым.

GTK и командная строка

Для этого проекта нам нужно использовать Beagle и – не удивляйтесь – GTK, поэтому нажмите правой кнопкой мыши на References на панели слева (сразу над Resources, AssemblyInfo.cs и Main.cs) и выберите Edit References. В появившемся окне переключитесь на вкладку Packages, где выберите Beagle (версия 0.0.0.0 – я предупреждал, что он далеко не стабилен, не так ли?) и GTK-sharp (версия 2.10.0.0). Эти номера просто сообщают, какие версии установлены в данный момент – если у кого-то другая версия Beagle, Mono преспокойно обойдется той, что есть.

Тем самым мы запросили для использования библиотеки Beagle и GTK#, но чтобы действительно их использовать, необходимо прописать операторы using в начале исходного кода. По умолчанию, единственная строка using

using System;

Библиотека System включает основные функции для написания консольного приложения, включая доступ к самой консоли для обеспечения ввода-вывода. Мы хотим включить в этот список Beagle и GTK, так что добавим следующие две строки:

using Beagle;
using Gtk;

В методе Main() необходимо вызвать Beagle и сказать ему, что искать. Это можно сделать через Query, специальный объект Beagle. Этот объект также умеет запускать наши собственные методы при наступленииопределенных событий – например, найдя что-нибудь, отвечающее поисковому запросу. Вот как все это выглядит в C# – поместите этот код туда, где находился Console.WriteLine():

// Создаем новый запрос и добавляем два метода обработчика событий
 Query q = new Query();
 q.HitsAddedEvent += OnHitsAdded;
 q.FinishedEvent += OnFinished;
// Сообщаем Beagle, что запрос содержится в первом параметре командной строки
 q.AddText(args[0]);                                                       
// Начинаем поиск
 q.SendAsync();

В общем, несложно, но это еще не вся сказка: найдя файл, RSS-ленту или что там еще совпадет с нашим запросом, Beagle всегда в вызывает метод OnHitsAdded(), а закончив поиск, вызовет метод OnFinished() – чтобы код скомпилировался, необходимо реализовать оба этих метода. Для успешной компиляции достаточно вставить два пустых метода:

 static void OnHitsAdded (HitsAddedResponse response) { }
 static void OnFinished(FinishedResponse response) { }

Объекты HitsAddedResponse и FinishedResponse вообще-то дают массу полезной информации, но пока мы их проигнорируем. Продолжим: нажмем F8, чтоб скомпилировать первый релиз Poochy (храбро назовем его 0.1), затем откроем окно терминала и перейдем в /каталог/где/находится/poochy/bin/Debug. Пора протестировать нашу ищейку.

Poochy необходимо запускать из командной строки, потому что он читает arg[0], который мы должны передать из командной строки. Запустите следующую команду:

mono poochy.exe foo

Эта команда заставит Beagle искать в своем кэше аргумент 'foo', и для выполнения запроса понадобится около секунды (замените foo на что-нибудь заведомо присутствующее в вашем кэше Beagle). Но тут вы обнаружите, что ничего не выводится – ни файлов, ни чего-либо еще, содержащего 'foo'. Чья это проблема – Beagle или наша? Beagle вне подозрений, значит, проблема в нашем коде. Расследуем...

Гонка на время

Наша программа не работает по двум причинам. Более заметная из них – пуст метод OnHitsAdded(), вызываемый Beagle при нахождении совпадения, в который Poochy должен вывести результат. Поэтому Mono безмолвствует, даже и получив что-то от Beagle. Проще всего заставить Poochy выводить URL каждого найденного Beagle объекта, например, так:

foreach(Hit hit in response.Hits)
 {
  Console.WriteLine("Hit: " + hit.Uri);
 }

Если вы запустите программу, то увидите, что она опять ничего не выдает, хотя Beagle должен бы найти то, что вы просили. Проблема том, что наш поиск использует метод SendAsync(), то есть Beagle выполняет поиск асинхронно. Мысленно прокрутите программу: компьютер выполняет первую строчку, переходит ко второй, затем к третьей, и так далее до конца. Конечно, иногда программа перескакивает с места на место, но смысл в том, что в один момент времени исполняется одна строка кода.

У Beagle есть два способа поиска: синхронный и асинхронный. Первый означает «выполняй поиск, а я буду ждать его окончания», а второй – «начинай поиск, а я продолжу выполнение программы; когда закончишь, прерви меня». Они также известны как блокирующий и неблокирующий, потому что синхронный метод блокирует выполнение следующей строки, пока сам не завершит работу, в то время как асинхронный метод позволит вам выполнять код, а сам будет работать параллельно.

Вам необходимо это знать, чтобы учесть такую возможность: вдруг ваша программа завершится раньше, чем асинхронный метод успеет что-либо сделать? То есть завершится до того, как Beagle найдет какое-либо совпадение? Ответ состоит в том, что программа успешно отключится, и вызванный метод сгинет, не успев ничего напечатать. Вот почему мы не получили никакого результата, и, значит, нам надо ждать, пока Beagle завершит поиск.

Вот здесь на сцену и выходит GTK. Когда вы используете программы с графическим интерфейсом, приложение ждет, пока вы чтонибудь сделаете: выберете пункт меню или хотя бы наведете курсор мыши на кнопку. Фактически, любое действие посылает сигнал приложению, чтобы оно смогло отреагировать, то есть приложение просто ждет сигнала. Очевидно, такие приложения не отключаются, выполнив то или иное действие, иначе, например, в Abiword вам пришлось бы непрерывно что-то печатать, чтобы редактор не закрылся. Вместо этого они используют так называемый главный цикл [сообщений], который выглядит примерно так:

while (1) {
  LookForSignals();
  ActOnSignals();
}

Вы, конечно, сообразили, что это бесконечный цикл, но он практически не потребляет процессорное время и позволяет другим частям приложения (графическому интерфейсу или, в нашем случае, Beagle) работать на полную катушку. Нам того и надо, вот почему мы собираемся сесть на главный цикл GTK: пусть наша программа простаивает, пока Beagle производит поиск и вывод результата. Чтобы это сделать, добавим в конец метода Main() две строчки:

Gtk.Application.Init();
Gtk.Application.Run();

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

Hit: file:///home/paul/foo.xml
Hit: file:///usr/share/doc/tzdata-2006m/tz-link.html
Hit: file:///usr/share/gtk-doc/html/libuser/libuser-config.html
Hit: file:///usr/share/doc/nant-0.85/releasenotes.html
Hit: file:///usr/share/gtk-doc/html/libgnome/libgnome-gnomeconfig.html
Hit: file:///usr/share/gtk-doc/html/gtk/gtk-question-index.html
Hit: file:///usr/share/gtk-doc/html/gtk/gtk-Resource-Files.html

Два шага вперед, один назад

Кроме основного результата, мы также получили побочный: выхода из приложения не происходит. Когда Beagle вернет все возможные результаты, Poochy будет по-прежнему чего-то ждать. Вы видели, что основной цикл приложения – бесконечный, и коль скоро мы вызвали метод Application.Run(), то вошли в состояние вечного исполнения. Пока программа не даст сбой, машина не перезагрузится или мы не нажмем Ctrl+C, Poochy так и будет находиться в этом состоянии.

Может, вам того и надо – к примеру, вы хотите периодически проверять ресурс или дожидаться, пока данные поиска доберутся до вас через сокет. Но Poochy задуман как программа, выполняющая поиск по требованию, а затем завершающаяся. Мы уже написали заглушку метода OnHitsAdded, а теперь займемся методом OnFinished(), который покамест пуст.

Когда Beagle вернет все результаты, он посмотрит, зарегистрирован ли метод для свойства FinishedEvent. Мы уже делали это с нашим методом OnFinished(), который вызывается по окончании работы Beagle. А сейчас нам нужно сообщить GTK, чтобы программа покинула основной цикл, тогда наше приложение сможет корректно завершиться. Это довольно просто – вот как выглядит новый метод OnFinished():

 static void OnFinished(FinishedResponse response)
 {
   Application.Quit();
 }

Application.Quit() – это GTK-метод, который прерывает главный цикл, очищает все использованные GTK-ресурсы (в нашем случае их немного, так как мы не работаем с графикой), затем передает управление нашему приложению сразу же после строки Application.Run() в методе Main(). Поэтому путь работы программы будет следующим: Main() > SendAsync() > Application.Run() > основной цикл GTK > OnFinished() > Application.Quit() > Main().

Скомпилируйте, запустите и восхититесь собственным умом: ваша программа делает все, что задумано, и потребовала всего десять строк значащего кода!

Poochy выводит на экран все URL, RSS-ленты, документы OpenOffice.org, MP3, презентации PowerPoint, заметки Tomboy, исходный код, JPEG файлы, приложения... (глубокий вдох)... email, видео, встречи, zip-файлы и установленные пакеты на вашей системе, которые соответствуют запросу, передаваемому в AddText(). А если вас интересуют только файлы, находящиеся на вашем диске, а не все что ни попадя?

Погодите... еще не все!

Два урока назад мы рассматривали метод EndsWith(), применимый к строкам, который принимает строку в качестве аргумента и возвращает true, если строка А оканчивается строкой B. Например:

string foo = "bar";
bool baz = foo.EndsWith("r");

Когда этот код выполнится, переменная baz установится в true, так как foo оканчивается на 'R'. В .NET также есть метод StartsWith(), который действует наоборот – возвращает true, если одна строка начинается с другой. Выходит, мы можем заставить наш код выводить только файлы, поменяв метод OnHitsAdded() на следующий:

static void OnHitsAdded (Beagle.HitsAddedResponse response)
 {
   foreach(Hit hit in response.Hits)
    {
      if (hit.Uri.StartsWith("file"))
       {
        Console.WriteLine("Hit: " + hit.Uri);
       }
    }
 }

Это метод грубой силы: Beagle ищет все подряд, а мы фильтруем результаты перед выводом. Но Beagle можно научить искать и конкретный тип совпадений, с помощью метода AddHitType(): он позволит описать то, что вы ищете, обычным текстом. Корректные параметры включают Application, Calendar, Contact, Feeditem (для RSS), Image, IMLog, MailMessage и – вот оно! – File.

Вернувшись к методу Main(), найдите строку q.AddText(args[0]) и добавьте перед ней строку:

q.AddHitType("File");

Запустите программу, и увидите, что выводятся только файлы – никакой переписки из чатов, засоряющей результаты! LXF

Часто задаваемые вопросы

Mono FAQ – часть I

Если тонкие моменты Mono вызывают у вас вопросы, то вот ответы на них...

  • Не понял. Зачем мы пометили Reference, а потом еще и прописали строку using?

Добавление чего-то через Reference позволяет воспользоваться соответствущим кодом; добавление чего-то с помощью строки using позволяет сэкономить набор кода при вызове методов. Для подключения Beagle необходимо добавить его как Reference – тогда станут доступны его объекты и методы, и мы можем немедля их использовать, но только под полным именем, включающим ссылку на пространство имен. Строка using Beagle; сообщает .NET, что когда мы пишем Query, подразумевается Beagle.Query. В нашем случае экономия невелика; а вот, например, алгоритм SHA1 в .NET находится в пространстве System.Encryption.Cryptography, и пришлось бы писать System.Encryption.Cryptography.SHA1CryptoServiceProvider foo = new System.Encryption.Cryptography.SHA1CryptoServiceProvider() – согласитесь, довольно утомительно! Вместо этого можно поместить в начале using System.Encryption.Cryptography;, затем SHA1CryptoServiceProvider foo = new SHA1CryptoServiceProvider(). Помните, что MonoDevelop по умолчанию добавляет System в Reference.

  • Почему мы используем +=, чтобы добавить метод к событию Beagle? Я думал, что += для добавления значения к переменной.

Да, += используется для сложения переменных, но почему бы не расширить эту метафору на события, происходящие во время работы Beagle, и не использовать += для добавления методов. Это также известно как подписка. Используя +=, вы можете 'подписать' несколько методов на одно событие – Mono просто запустит их по одному последовательно в порядке их добавления.

  • Зачем нужен файл AssemblyInfo.cs?

Вы будете смеяться, но там и правда информация о вашей сборке! В терминах .NET сборка может быть как разделяемым объектом (SO файл в Linux или DLL в Windows), так и исполняемым файлом. Номер версии вашего исполняемого файла, имя программиста и другая информация находится в конечном двоичном файле poochy, и все это вы устанавливаете в AssemblyInfo.cs.

  • Надо ли располагать открывающую и закрывающую фигурные скобки на отдельных строках?

Нет – вовсе нет! Лично я люблю ставить { на той же строке, что и оператор, ей предшествующий. Этот стиль известен как Единственно Верный Скобочный Стиль, потому что его приняли Брайан Керниган и Деннис Ритчи, когда они изобрели язык программирования С. Однако MonoDevelop по умолчанию использует стиль BSD, когда каждая скобка располагается на своей строке. Выбирайте, что вам больше нравится, главное – соблюдать единый стиль во всей программе!

Персональные инструменты
купить
подписаться
Яндекс.Метрика