Пример обработки ошибок можно видеть в коде программы просмотра текстовых файлов, рассмотренной в предыдущей статье, где существует вероятность возникновения проблемы при открытии заданного файла.
private void open_file( string filename )
{
try
{
string text;
size_t size;
FileUtils.get_contents( filename, out text, out size );
this.text_view.buffer.set_text( text, (int) size );
}
catch( Error e )
{
stderr.printf( "Ошибка: %s\n", e.message );
}
}
|
В конструкции try {} catch {} не видно никаких отличий от аналогов из C++, C# и Java. Особенности обнаруживаются на более низком уровне – в реализации. В библиотеке GLib имеется средство управления исключениями времени выполнения GError. Vala преобразует его в более привычную "современную" форму, но сам по себе GError изначально был предназначен для обработки так называемых восстановимых ошибок времени выполнения, о которых ничего не известно до начала выполнения, и которые не приводят к аварийному завершению программы. Поэтому не следует напрямую использовать GError для тех исключительных ситуаций, которые можно "предсказать", например, передача отрицательного числа в функцию, которая требует параметр только со значением, большим нуля.
При возникновении необходимости в написании специфического кода для обработки исключений общая схема работы точно такая же, как при использовании любого другого языка из "C-группы". Все функции, являющиеся потенциальными источниками исключений, определяются с ключевым словом throws. Везде, где это необходимо в коде, записываются команды генерации исключений throw. Вызовы потенциально "опасных" функций помещаются в блоки try-catch.
Отличия проявляются при определении типов обрабатываемых исключений (ошибок). Исключения обладают тремя характеристиками: домен (domain), код (code) и сообщение (message). Домен определяет тип ошибки. Ближайший аналог – подкласс класса Exception в языке Java. Каждый домен исключения может содержать один или несколько кодов ошибок:
errordomain IOError // имя домена
{
FILE_NOT_FOUND // код ошибки
}
|
Таким образом, можно определить не только обобщённый тип возникающей проблемы, но и конкретную ошибку. Что касается сообщений, то мы уже встречались с ними – это текст, выводимый при возникновении исключительной ситуации.
Для того чтобы обрабатывать исключения в программе, вы должны определить домены с кодами ошибок и написать соответствующие обработчики для доменов. Каждому домену ошибок соответствует отдельный блок catch. Кроме того, после блока try и всех необходимых блоков catch можно добавить необязательный блок finally, который будет выполняться всегда, даже при отсутствии ошибок и их обработки, потому что иногда требуется освобождение ресурсов, захваченных внутри блока try.
Всё, сказанное выше, можно проиллюстрировать следующим примером:
errordomain ErrType01
{
ERROR_CODE_01
}
errordomain ErrType02
{
ERROR_TYPE_02
}
public class MyClass : GLib.Object
{
public static void myfunc_throw() throws ErrType01, ErrType02
{
throw new ErrType01.ERROR_CODE_01( "Ошибка с кодом 01" );
}
public static void myfunc_catch() throws ErrType02
{
try
{
myfunc_throw();
}
catch( ErrType01 e )
{
// Здесь обрабатываются ошибки из домена ErrType01
}
finally
{
// Освобождение ресурсов и прочие необходимые операции
}
}
public static int main( string[] args )
{
try
{
myfunc_catch();
}
catch( ErrType02 e )
{
// Здесь обрабатываются ошибки из домена ErrType02
}
return 0;
}
}
|
Здесь метод myfunc_throw может генерировать исключения из обоих доменов, определённых в программе. Метод myfunc_catch способен генерировать исключения только второго типа, поэтому обязан обеспечить обработку ошибок из домена ErrType01. В свою очередь, метод main обеспечивает обработку всех ошибок, которые могут возникать при работе метода myfunc_catch.
Поток (thread) в языке Vala не определяется во время компиляции, это просто фрагмент кода, который программа во время выполнения будет пытаться выполнить как новый поток. Многопоточность реализована с помощью статических функций класса Thread из библиотеки GLib. Рассмотрим небольшой и очень простой пример.
01: using GLib;
02: public class MyThread : GLib.Object
03: {
04: public static void * thread_fun()
05: {
06: stdout.printf( "Поток активизирован... \n" );
07: return null;
08: }
09: public static int main( string [] args )
10: {
11: if( !Thread.supported() )
12: {
13: stdout.printf( "Без поддержки потоков выполнение невозможно \n" );
14: return -1;
15: }
16: try
17: {
18: Thread.create( thread_fun, false );
19: }
20: catch( ThreadError ex )
21: {
22: return -1;
23: }
24: return 0;
25: }
26: }
|
При компиляции необходимо подключить поддержку многопоточности, используя соответствующий ключ:
valac --thread -o thread-test mythread.vala |
Отметим, что исходный код был сохранён в файл mythread.vala, а для конечного выполняемого файла задано другое имя (thread-test) с помощью ключа -o.
Результат работы программы показан на рисунке 1.
Рисунок 1. Выполнение многопоточной программы
Этот пример демонстрирует лишь возможность работы с потоками при программировании на языке Vala. В рамках данной статьи невозможно рассмотреть все аспекты и проблемы многопоточности: синхронизацию, кооперацию потоков и т.д.
При компиляции программы просмотра мы уже убедились, что использование библиотек не вызывает никаких затруднений. В командной строке записывается ключ --pkg, после которого перечисляются требуемые библиотеки. Строго говоря, это не сами библиотеки, а vapi-файлы, о которых я уже говорил. Например, для нашего примера:
valac --pkg gtk+-2.0 text-file-viewer.vala |
в программе будут использованы все определения из файла gtk+-2.0.vapi, а также все пакеты (библиотеки), от которых зависит gtk+-2.0. Если такие зависимости существуют, то они перечисляются в файле gtk+-2.0.deps. Всю остальную работу по подключению необходимых C-библиотек компилятор Vala выполняет автоматически, без нашего вмешательства.
С созданием собственных библиотек дело обстоит гораздо сложнее. Мы уже выяснили, что с технической точки зрения "библиотека Vala" означает "C-библиотека". Таким образом, в действительности вам придётся создавать библиотечный файл языка C. Это можно сделать двумя способами. Первый – это получение объектных файлов из исходных кодов Vala с помощью ключа компилятора -c (компилировать, но не связывать):
vala -c <список_vala-файлов> |
и последующим использованием системных утилит обслуживания библиотечных файлов ar или комплекта для создания динамических библиотек libtool. Например:
ar rc <имя_библиотеки> <полученные_объектные_файлы> |
Второй способ предполагает генерацию промежуточного C-кода (в предыдущей статье мы уже пользовались ключом -C):
valac -C <список_vala-файлов> |
и компиляцию полученных после этого файлов с целью получения динамических (shared) библиотек:
gcc -o my-library.so --shared -fPIC <список_полученных_С-файлов> |
Здесь ключ -fPIC позволяет сгенерировать позиционно-независимый код (Position-Independent Code), который необходим для создания динамически связываемой библиотеки.
Обзор штатных инструментальных средств и утилит
В дистрибутивный пакет Vala помимо компилятора, который мы уже рассмотрели достаточно подробно, включены следующие полезные программы.
vala-gen-introspect – инструмент для извлечения метаинформации о библиотеках, основанных на GObject. Эта информация может использоваться для создания vapi-файлов, т. е. для привязки библиотеки с целью применения её в Vala-программах. Имя библиотеки должно быть задано в формате pkg-config.
vapigen – создаёт vapi-файлы с помощью метаинформации о библиотеках и прочей дополнительной информации.
vala-gen-project – простая графическая среда для создания Vala-проекта. Созданный в ней проект будет содержать скрипты генерации программ с помощью auto-инструментов (autoconf, automake и проч.), которые могут послужить основой для построения более сложной системы.
Справочную информацию об этих утилитах и о компиляторе можно получить из соответствующих man-страниц.
В дистрибутивный пакет Vala помимо компилятора, который мы уже рассмотрели достаточно подробно, включены следующие полезные программы.
vala-gen-introspect – инструмент для извлечения метаинформации о библиотеках, основанных на GObject. Эта информация может использоваться для создания vapi-файлов, т. е. для привязки библиотеки с целью применения её в Vala-программах. Имя библиотеки должно быть задано в формате pkg-config.
vapigen – создаёт vapi-файлы с помощью метаинформации о библиотеках и прочей дополнительной информации.
vala-gen-project – простая графическая среда для создания Vala-проекта. Созданный в ней проект будет содержать скрипты генерации программ с помощью auto-инструментов (autoconf, automake и проч.), которые могут послужить основой для построения более сложной системы.
Справочную информацию об этих утилитах и о компиляторе можно получить из соответствующих man-страниц.