Как стать автором
Обновить

7-Zip из .NET или как я делал open source проект на CodePlex

Время на прочтение9 мин
Количество просмотров17K


Эта статья с одинаковым успехом может быть отнесена к блогам ".NET", «Open source» и «Я пиарюсь». После того. как я написал материал, стало ясно, что больше всего в ней «Open source»… Просьба сильно не бить, если я ошибся.

Итак, ниже пойдет рассказ о моем полуторагодичном опыте разработки библиотеки SevenZipSharp с открытыми исходниками, выложенной на CodePlex в феврале 2009. Эта библиотека — обертка над 7-Zip, позволяющая с легкостью его использовать в .NET.

Использование SevenZipSharp

В библиотеке два главных класса — SevenZipExtractor и SevenZipCompressor. Шаблон использования первого:
// Синхронная распаковка
using (var extr = new SevenZipExtractor(@"путь\к\архиву"))
{
  extr.Extracting += DoExtractingEvent();
  extr.ExtractArchive(@"куда\распаковывать");
  DoFinishEvent();
}

// Асинхронная распаковка
var extr = new SevenZipExtractor(@"путь\к\архиву");
extr.Extracting += DoExtractingEvent();
extr.ExtractionFinished += (s, e) => { DoFinishEvent(); extr.Dispose(); extr = null; };
extr.BeginExtractArchive(@"куда\распаковывать");


* This source code was highlighted with Source Code Highlighter.
Шаблон использования второго:
// Синхронная упаковка
var cmpr = new SevenZipCompressor();
cmpr.CompressDirectory(@"путь\к\пакуемой\папке", @"имя\архива");
DoFinishEvent();
cmpr = null;

// Асинхронная упаковка
var cmpr = new SevenZipCompressor();
cmpr.CompressionFinished += (s, e) => { DoFinishEvent(); cmpr = null; }
cmpr.BeginCompressDirectory(@"путь\к\пакуемой\папке", @"имя\архива");


* This source code was highlighted with Source Code Highlighter.

Не хочу превращать статью в документацию по SevenZipSharp, так что просто перечислю ее некоторые возможности:
  • Полная поддержка многопоточности
  • Оборачиваются все форматы архивов, поддерживаемые 7-Zip-ом
  • Неприхотливость к оборачиваемой библиотеке — никаких жестких привязок
  • Поддержка многотомных архивов при распаковке и упаковке
  • Распаковка большинства SFX архивов
  • Весь код тщательно снабжен комментариями
Как все началось

В феврале 2009 года мне понадобилось работать с 7-zip архивами в одном из платных проектов, над которым я трудился. Несколько дней я безуспешно искал готовое удобоваримое решение, но не нашел ничего лучше этой статьи на CodeProject. Зато прочитал много сообщений, где люди жаловались на его отсутствие. И тогда, собрав волю в кулак и отталкиваясь от найденной статьи, я мужественно пошел вперед принялся писать свою реализацию обертки над 7-Zip. Я решил выложить код на недавно открывшемся CodePlex под лицензией LGPLv3. Поначалу работа кипела, и я выпускал релиз за релизом раз в несколько дней (вы можете в этом убедиться сами в разделе «Other downloads» на странице загрузок). Потом мой пыл несколько поугас и я стал стабилизировать код. В сентябре 2009 релизы перестали выходить часто (я женился) и с тех пор я поддерживаю проект, как могу.

Рассматривался вариант с компиляцией 7-Zip в смешанную сборку (mixed assembly) с флагом /clr. Этот вариант был отвергнут, т.к. во-первых, интерфейс получился бы низкоуровневый и не пригодный для быстрого использования и все равно пришлось писать «добавку», а во-вторых, чтобы собрать код с флагом /clr:pure, требовалось бы переписывать много кода, и unmanaged части все равно остались.

Когда SevenZipSharp только появился, мне хотелось рассказать о нем потенциально заинтересованным пользователям-разработчикам. Я оставлял краткое описание библиотеки везде, где было можно: в ответах на вопросы StackOverflow, на программистских форумах (в т.ч. MSDN, Channel 9), в комментариях к той самой статье на CodeProject и даже на английской Wikipedia. Это все принесло результаты, и вскоре поисковые выдачи Google вышли по траффику на первое место. Думаю, рекламировать свои проекты должны все, иначе большинство попросту не узнает об их существовании. Эффективность рекламы оценивается по статистике загрузок и посещений, которая публично доступна.

SevenZipSharp и 7-Zip

Как многие знают, 7-Zip написан на C++ с небольшим количеством C и пресловутой ассемблерной функцией, вычисляющей CRC32, которую Игорь Павлов реализовал для x86, x86-64 и ARM. Какая либо документация по коду отсутствует, что в стиле русских программистов, участвующих в open source движении. Кода много, он совсем не прост и требуется некоторое время, чтобы разобраться в изобилии define-ов, интерфейсов и классов. Реализации алгоритмов сжатия называются кодеками (Codecs). Кодеки стандартным способом встраиваются в библиотеку, как подключаемые модули протоколов в мессенджерах ala Miranda/Pidgin. Архитектура 7-Zip неотделима от COM; именно это препятствует развитию p7zip — 7-Zip для POSIX систем, которым также занимается Игорь Павлов. В p7zip COM заменен костылем, который симулирует его работу, попутно объявляя половину типов windows.h. Сами алгоритмы написаны безупречно и очень стабильны, однако верхние уровни, как вы уже догадываетесь, оставляют желать лучшего. Если бы автор начал писать 7-Zip сейчас, думаю, он придумал архитектуру ядра более понятную, универсальную и портируемую, в идеале — на языке вроде C# или Java, хоть даже на Python (ну не годятся для этих целей плюсы, чего уж там).

Кстати, 7-Zip для конечных пользовтелей (инсталляция, которая качается с 7-zip.org) собирается Visual Studio 6 образца начала века. Файлы решений успешно преобразовываются в форматы VS2008/2010, и после замены компилятора C/C++ на более новый и активации всех флагов оптимизации (да-да, моя основная профессия — компиляторщик), а также использования профиля достигается ускорение около 15% (LZMA/LZMA2). На заметку…

Вот как SevenZipSharp оборачивает 7-Zip. Через COM-овский CreateObject создается объект, поддерживающий указанный интерфейс (IInArchive, IOutArchive). Из этого объекта дергаются нужные функции, и достигается желаемый результат (например, IInArchive.Extract(...)). Во время длительных операций из unmanaged кода вызываются managed callback-и, и это приводит к проблеме, которую я не сразу осознал — обработка ошибок. Например, из-за ошибки в callback-е или исключении в вызываемом callback-ом пользовательском событии исполнение операции падает без предупреждений и какой-либо вразумительной информации, кроме странного 32-битного кода ошибки. Я решил обернуть все callback-и try/catch-ами и заносить все возникшие исключения в стек ошибок, который в случае неудачи показывается пользователю. Если есть более изящное решение, прошу о нем рассказать.

Попытки рубить с плеча переписать весь код 7-Zip на C# энтузиастами предпринимаются регулярно, но ни одна не ушла дальше обсуждений. Переделывать алгоритмы с C++ на C# не выгодно: затраченные усилия и падение в скорости не окупаются кроссплатформенностью и религией, а переписать ядро с учетом всех тонкостей может только сам Игорь Павлов. Не буду голословен: LZMA на C#/.NET из LZMA SDK по замерам работает в 4 раза медленнее unmanaged алгоритма. Поэтому, пожалуй, лучшим в такой ситуации было сделать обертку с понятным и простым интерфейсом.

В один прекрасный момент мне захотелось заставить работать SevenZipSharp под Mono (GNU/Linux). И тогда проблема привязанности 7-Zip к COM проявила себя во всем великолепии. Необходимо было заново написать низкоуровневую часть библиотеки почти с нуля. Т.к. код 7-Zip, как я уже писал, специфичный, инструменты для автоматической обертки вроде SWIG оказались бесполезными, а чтобы они вообще заработали, мне пришлось сначала пройтись по всему коду препроцессором и убрать 10-ти этажные define-ы. В настоящее время я потихоньку пишу независимую от COM обертку.

Разработка

Наверное, многие начинающие C# разработчики повторяют одни и те же ошибки, и я не являюсь исключением. Когда мне стало известно о FxCop и StyleCop, я сразу попробовал их использовать. Казалось бы, логично поддерживать код библиотеки в хорошем состоянии. Однако, StyleCop по умолчанию выдает слишком много предупреждений, а настраивать его мне не хотелось, и он был сразу отброшен. FxCop использовался некоторое время, но в один прекрасный момент я поймал себя на мысли, что на элементарные изменения кода я трачу слишком много времени, чтобы соответствовать правилам, которые, по сути ни на что не влияют. Вывод, который я для себя сделал — эти инструменты важны для выработки индивидуального стиля программирования, но разработчикам-одиночкам увлекаться ими не стоит.

Изначально SevenZipSharp писался на Visual Studio 2008 и работал под 2-ым фреймворком. Даже тогда я понимал, что чем меньше версия .NET, тем меньше проблем будет использующим библиотеку. Жаль, что многие разработчики на CodePlex этого не понимают, начинают писать код, используя ультрасвоременные возможности .NET 4, и потом удивляются, почему у них так мало загрузок. Потом я узнал, что в Windows Mobile < 7 есть полноценный COM, и за несколько дней портировал SevenZipSharp на эти мобильные системы. Если кто-то не знает, фреймворки обычные и compact имеют ряд различий, и код без небольших изменений не будет собираться, а тем более работать. Поддерживать две почти идентичные ветки я считал неразумным, и решил эту проблему множественными #if/#else/#endif (стандартный подход в исходниках на C++).

Когда вышел Visual Studio 2010/C# 4, я обнаружил, что фичи новой версии языка можно эффективно применить к коду (например, появившиеся опциональные параметры устраняют 10+ перегрузок единственного логического метода). Для сохранения обратной совместимости я снова применил #if/#else/#endif. Код понемногу стал превращаться из изящных классов и ветвистого монстра. А когда пришла идея портировать SevenZipSharp на Mono, некоторые файлы с кодом я все-таки раздвоил, т.к. иначе сам бы через пару недель не смог в нем разобраться. В итоге, передо мной во всей красе встала проблема поддержки разных платформ и фреймворков в одном единственном файле. Пример:
#if !DOTNET20
    /// <summary>
    /// Unpacks the whole archive asynchronously to the specified directory name at the specified priority.
    /// </summary>
    /// <param name="directory">The directory where the files are to be unpacked.</param>
    /// <param name="eventPriority">The priority of events, relative to the other pending operations in the System.Windows.Threading.Dispatcher event queue, the specified method is invoked.</param>
#else
    /// <summary>
    /// Unpacks the whole archive asynchronously to the specified directory name at the specified priority.
    /// </summary>
    /// <param name="directory">The directory where the files are to be unpacked.</param>
#endif
    public void BeginExtractArchive(string directory
#if !DOTNET20
      , DispatcherPriority eventPriority
#if CS4
      = DispatcherPriority.Normal
#endif
#endif
)
    {
      SaveContext(
#if !DOTNET20
        eventPriority
#endif
      );
      (new ExtractArchiveDelegate(ExtractArchive)).BeginInvoke(directory, AsyncCallbackImplementation, this);
    }


* This source code was highlighted with Source Code Highlighter.

Отмечу, что разрабатывался SevenZipSharp стихийно. Если что-то хотелось добавить к функциональности, я брал и добавлял — и не советовался с начальниками, не согласовывал решения с руководством и т.д. В этом есть свои минусы, но зато баги исправлялись мгновенно и просьбы о новых функциях удовлетворялись в течение нескольких дней. Полная свобода действий — и настоящая учеба на своих ошибках.

Бонусы

От участия в open source проекте на CodePlex появились неожиданные приятные сюрпризы. Во-первых, я заметил, что компания JetBrains, создатель ReSharper, выдает бесплатные лицензии разработчикам программного обеспечения с открытым исходным кодом. Я попытал счастья и не пожалел — мне действительно выдали лицензию. ReSharper оказался незаменимым помощником в написании кода, и я всем его советую. Во-вторых, с недавнего времени CodePlex показывает рекламу на страницах проектов (по желанию их владельцев). Доход от рекламы можно либо жертвовать на благие цели, либо присваивать себе. Я выбрал второй вариант, и получаю около 10$ в месяц. В-третьих, когда SevenZipSharp обрел популярность, мне стали предлагать спонсорство компании, разрабатывающие полезные инструменты для C#/.NET программистов, такие как NDepend и SciTech .NET Memory Profiler. В-четвертых, небольшие деньги приносит кнопка «Donate». Никто пока больше 10$ не жертвовал, но даже это приятно и стимулирует.

Воодушевляет, когда в письмах благодарят за библиотеку, сообщают, что используют ее в реальных и весьма известных проектах (например, Stardock). Иногда мне приходят письма от людей, с предложениями начать работать над библиотекой вместе. Человек обычно полон энтузиазма, уверяет, что вместе будет здорово и т.п. После ответного в письма, в котором я пишу, что сразу никому пароль от SVN не дам, спрашиваю, а что человек вообще умеет, и описываю примерные планы развития проекта на будущее, никто со мной пока не связывался. Мне это кажется странным, возможно, психологию таких людей мне объяснят в комментариях.


Типичное письмо без продолжения

Поскольку речь зашла о людях, расскажу о публике на CodePlex. Несколько раз мне жертвовали код, причем всего один раз — по правилам, через патч. Порой давали дельные советы, подсказывали, что можно сделать лучше. Очень приятно, когда ошибки исправляешь не ты, а другие пользователи, и делятся потом багфиксом. Однако часто о баге заявляют не в Issue Tracker, а в обсуждения (Discussions), даже если баг очевидный. Приходится регулярно вчитываться в вопросы и решать, чей это косяк, библиотеки или криворукого пользователя. Впрочем, иногда появлялись «участники», заводящие сразу с десяток багов, из которых в лучшем случае пара действительно стоящие, а остальные являются просьбами добавить лишнюю функциональность, которая кроме самих «участников» никому не нужна. Бывает, что завел человек баг вроде «не работает распаковка», спрашиваешь у него в комментариях, какая версия библиотеки, как воспроизвести ошибку, а тот уже давно забыл про SevenZipSharp и не отвечает. Совсем.

Отдельно веселят люди, оставляющие оценки (звездочки на CodePlex, от 1 до 5). Бесит, когда ставят 2 и не объясняют почему. Впрочем, также бесит когда ставят 2 и пишут, что мол вообще ничего не работает и библиотека ваша говно. К счастью, с SevenZipSharp это происходит редко, в отличие от других популярных проектов, которые, я уверен, не заслуживают таких оценок.

Итоги

Оглядываясь назад, я вижу, что связался с SevenZipSharp не зря. Получены и бесценный опыт, и некоторые выгоды. Если вы спросите, а стоит ли разрабатывать свой open source проект «для души», я без раздумий отвечу — конечно!

Спасибо за внимание.
Теги:
Хабы:
+129
Комментарии46

Публикации