Skip to content

Единственный способ программировать на C

License

Notifications You must be signed in to change notification settings

safinaskar/libsh

Repository files navigation

libsh - это библиотека на C и для C. Её можно использовать и в C++, но см. далее. Её, в принципе, можно использовать в любом языке, если написать биндинги.

libsh должна работать в любой операционной системе, в том числе в Windows и в UNIX-подобных системах (Mac OS X, GNU/Linux). Тем не менее, в первую очередь либа предназначена для UNIX-подобных систем.

Лицензия: GPLv3+

libsh - это:

  • Исключения для C. Исключения реализованы с использованием только стандартных средств C, т. е. libsh в прямом смысле добавляет исключения в C, и это будет работать в любом компиляторе и ОС (реализовано с помощью setjmp/longjmp/sigsetjmp/siglongjmp)
  • Обёртки вокруг стандартных функций C, например, fopen, и стандартных функций POSIX (только при их наличии), например, open, которые бросают вышеупомянутые исключения в случае ошибок
  • Дополнительные функции, реализовывающие часто нужные операции на UNIX-подобных системах (если у вас такая система), например, копирование данных из одного файлового дескриптора в другой. Эти функции реализованы с помощью вышеупомянутых обёрток, поэтому они тоже могут бросать исключения

Сборка и установка на UNIX-подобных системах (Mac OS X, GNU/Linux, Cygwin, MinGW MSYS и т. д.)

Вам нужен cmake версии как минимум 3.1.0, make и компилятор C. WARNING! cmake 3.1.0 только что вышел, скорее всего его нет в репозитории вашей ОС. Итак, идём в https://github.com/safinaskar/libsh/releases , скачиваем последний libsh-extended-$VERSION.tar.gz (разумеется, под $VERSION я подразумеваю конкретный номер версии) и:

$ tar -xf libsh-extended-$VERSION.tar.gz
$ mkdir libsh-build
$ cd libsh-build
$ cmake -DCMAKE_INSTALL_PREFIX=/путь/куда/ставить ../libsh-$VERSION
$ make
$ make install # Если /путь/куда/ставить требует прав рута, то эту команду нужно запускать от рута

Если очень хочется собрать и установить именно из git'а, что ж, можно и из git'а, только меньше шансов, что соберётся:

$ git clone git@github.com:safinaskar/libsh.git
$ cd libsh
$ ./dev.mk
$ mkdir ../libsh-build
$ cd ../libsh-build
$ cmake -DCMAKE_INSTALL_PREFIX=/путь/куда/ставить ../libsh
$ make
$ make install # Если /путь/куда/ставить требует прав рута, то эту команду нужно запускать от рута

Сборка на нативном Windows с помощью Visual C++ (сборка на Cygwin и MinGW MSYS описана выше)

Установку на Windows я не описываю, так как не совсем понятно, куда и зачем нужно этот самый libsh устанавливать. В Program Files что ли? (Впрочем, если вы со мной не согласны, смело пишите мне свой feedback [e-mail в конце], мне бы очень хотелось узнать, как устроена культура установки маленьких проектов свободного ПО на нативном Windows, куда их принято устанавливать и принято ли вообще.) Итак, сборка на Windows. У вас должен быть установлен cmake версии как минимум 3.1.0 (он вышел совсем недавно, убедитесь, что у вас как минимум 3.1.0!) и Visual C++. Идём в https://github.com/safinaskar/libsh/releases , скачиваем последний libsh-extended-%VERSION%.zip (из git'а собрать не сможете, разумеется, под %VERSION% я подразумеваю конкретный номер версии), распаковываем, открываем Visual C++, там открываем консоль Visual C++ и в неё набираем:

> cd \путь\к\libsh-%VERSION%
> mkdir ..\libsh-build
> cd ..\libsh-build
> cmake ..\libsh-%VERSION%
> cmake --build .

Работа с либой

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

Исключение бросается с помощью SH_THROW. Исключения в libsh пустые, т. е. они не несут никакой информации. Можно сказать, что исключение в libsh - это просто сигнал из точки возникновения исключения в точку его ловли. Поэтому исключение бросается попросту так:

  SH_THROW;

Никаких аргументов к SH_THROW не надо.

Есть конструкция SH_CTRY { ... } SH_CATCH { ... } SH_CEND;. Она соответствует обычной try/catch-конструкции из C++, Java и C#. SH_CATCH - это те действия, которые должны быть выполнены ровно в случае возникновения исключения в SH_CTRY. Если в SH_CTRY будет брошено исключение, управление передастся блоку SH_CATCH, а после его выполнения продолжится обычный ход программы. В противном случае блок SH_CATCH будет пропущен.

Есть конструкция SH_FTRY { ... } SH_FINALLY { ... } SH_FEND;. Она соответствует try/finally из Java и C#. SH_FINALLY - это действия, которые должны быть выполнены в любом случае, даже в случае возникновения исключения в SH_FTRY. Если в SH_FTRY будет брошено исключение, управление передастся блоку SH_FINALLY, а после его выполнения исключение будет брошено опять. В противном случае блок SH_FINALLY всё равно будет выполнен, но исключение в его конце брошено не будет.

Если исключение не будет поймано, будет сделан exit (EXIT_FAILURE). Это действие можно поменять с помощью sh_set_terminate.

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

#define _POSIX_C_SOURCE 1

#include <stdio.h>
#include <stdlib.h>
#include <libsh.h>

int
main (int argc, char *argv[])
{
  sh_init (argv[0]);
  FILE *fp = fopen ("file", "w");
  if (fp == NULL)
    {
      perror ("fopen");
      SH_THROW;
    }
  SH_FTRY
    {
      if (fprintf (fp, "hello\n") < 0)
        {
          perror ("fprintf");
          SH_THROW;
        }
    }
  SH_FINALLY
    {
      if (fclose (fp) == EOF)
        {
          perror ("fclose");
          SH_THROW;
        }
    }
  SH_FEND;
  exit (EXIT_SUCCESS);
}

В libsh есть обёртки вокруг стандартных функций C и POSIX, которые в случае ошибки пишут сообщение в stderr и бросают исключение (с помощью sh_set_err можно заменить stderr на что-нибудь другое). Например, sh_x_fopen делает fopen и, если это не удалось, пишет сообщение и бросает исключение. Ещё раз подчеркну, что действия происходят в следующем порядке:

  • Делаем fopen
  • В случае ошибки пишем сообщение (да, именно здесь, а не в точке ловли исключения, как это делается обычно в C++!)
  • Бросаем исключение (пустое!)
  • В другом месте программы это исключение ловится, т. е. мы попадаем в блок SH_FINALLY или SH_CATCH. Или, если исключение так никто и не поймал, выполняется exit (EXIT_FAILURE)

sh_x_fopen реализована примерно так (настоящую реализацию можно посмотреть в funcs.c, когда он сгенерируется):

FILE *
sh_x_fopen (const char *SH_RESTRICT pathname, const char *SH_RESTRICT mode)
{
  FILE *result = fopen (pathname, mode);
  if (result == NULL)
    {
      sh_throw ("fopen: " "%s", pathname); // sh_throw - это, можно сказать, fprintf + SH_THROW
    }
  return result;
}

Пример программы с использованием обёрток (она делает то же самое, что и предыдущая):

#define _POSIX_C_SOURCE 1

#include <stdlib.h>
#include <libsh.h>

int
main (int argc, char *argv[])
{
  sh_init (argv[0]);
  FILE *fp = sh_x_fopen ("file", "w");
  SH_FTRY
    {
      sh_x_fprintf (fp, "hello\n");
    }
  SH_FINALLY
    {
      sh_x_fclose (fp);
    }
  SH_FEND;
  exit (EXIT_SUCCESS);
}

Таким образом, в libsh принята следующая идеология: в точке возникновения ошибки мы знаем, что именно произошло. Т. е. если fopen свалился, мы знаем, что свалился именно fopen, а не какая-нибудь другая функция, мы знаем значение переменной errno. На основании всей этой информации мы пишем информативное сообщение в stderr. Дальнейшему коду, на мой взгляд, уже не надо знать, что именно произошло (ведь мы уже выдали информацию в stderr), поэтому далее бросается просто пустое исключение. Оно как бы говорит: "Случилось что-то плохое, но что именно - не важно, т. к. инфа уже выдана в stderr, поэтому осталось лишь сделать некий откат (выполнить блоки SH_FINALLY) и продолжить работу либо завершить её".

В C++ (и других языках) обычно всё принято по-другому: если сообщение пишется в stderr, оно пишется обычно в точке ловли исключения, а не бросания, и исключение содержит в себе информацию об ошибке.

Итак, почему же я сделал именно так? Что ж, так проще. Не надо думать, о том как хранить информацию об исключении, и что она должна собой представлять. В моих программах мне бывает нужна только такая (моя) упрощённая система обработки ошибок.

Такая необычная система работы с ошибками является самым спорным моментов в libsh, ваш feedback будет очень кстати (e-mail в конце). Если сможете мне привести пример, где мой подход не срабатывает - буду очень рад (желательно в простом C-подобном коде, с минимальным использованием фич C++ или вообще без них, в простом коде в стиле GNU coreutils, а не в энтерпрайзном коде с мощным ООП, паттернами и т. д.).

Также в libsh есть функции для типичных операций в UNIX-подобных системах, они находятся в etc.c, там же смотрите документацию (например, sh_multicat).

libsh можно использовать и в C++, но см. коммент "C vs C++ statement" в ex.c.

Дальнейшую документацию см. в комментах, отмеченных /// (в том числе //@ ///) в коде, в начале funcs.in и в строчках, помеченных "docs:" в funcs.in.

В libsh есть ещё очень много того, что не описано в этом README. Обо всём об этом рассказано в других файлах, например, в etc.c, просто на данный момент недостаточно подробно.

libsh недописана, есть фичи, которые я ещё хочу реализовать. Также, явно недописанны доки. Тем не менее, уже сейчас я рад любому feedback'у от вас. Пишите мне на safinaskar@mail.ru .

About

Единственный способ программировать на C

Resources

License

Stars

Watchers

Forks

Packages

No packages published