Единственный способ программировать на C
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
.gitignore
CMakeLists.txt
COPYING
README.markdown
cxx.hpp
dev.mk
etc.c
ex.c
expand-table.sh
funcs.in
gen-funcs.sh
libsh.h
tpm-control

README.markdown

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 .