LXF98:Mono

Материал из Linuxformat.

Перейти к: навигация, поиск
C# для начинающих

Содержание

Mono: Назад в Unix

Хотя C# и новый, и передовой, Mono стоит на плечах уродливого монстра Posix. Пол Хадсон пробует заставить Unix-натуру Linux сработаться с .NET...

Имеет ли место садомазохизм в мире компьютеров? Если да, то вот он: я покажу вам, как заставить C# идти бок о бок с Posix и выиграть. Да, Posix – этот дурно задуманный процесс стандартизации, сбивающий с толку программистов, игнорируемый конечными пользователями, и все же подпирающий Linux и другие Unix-подобные ОС. Posix – это набор системных вызовов, интерфейсов и сигналов, определяющий, как мы, разработчики, взаимодействуем с операционной системой. Действующий стандарт Posix весьма обширен, но по сути мы должны заботиться только вот о чем: если вы пишете Posix-совместимый код, он должен работать в любой Posix-совместимой ОС.

Как ни странно, список совместимости включает Windows Vista, точнее, большинство основанных на NT версий Windows, коль скоро они имеют установленными службы Services for Unix. Но с нашей точки зрения важно то, что Linux, FreeBSD, OpenBSD и Syllable практически, а AIX, HP-UX, Minix, OS X и Solaris – полностью поддерживают Posix. Короче, использование функциональности Posix может заставить вас рвать на себе волосы, но, по крайней мере, вы в хорошей компании!

Posix и вы

Имеется два типа людей, в основном использующих Posix: конечные пользователи и разработчики. Значит, практически все! Рассмотрим каждый тип отдельно, начав с пользователя. Да, я помню свои слова, что конечный пользователь игнорирует Posix, но это не совсем верно – фактически, вы используете преимущества инструментов и интерфейсов Posix при каждом обращении к командной строке. Видите ли, Posix заправляет едва ли не всем, что даруют Unix-подобные операционные системы – как работает ваша командная строка, есть ли инструменты типа awk и компиляторов, как происходит взаимодействие программ через каналы. Пусть даже конечные пользователи ничего не знают и знать не хотят о Posix, они обязательно опираются на его набор функций! Что касается разработчиков, то любой из пишущих код на С должен работать с одним из многих интерфейсов ядра и вызовами стандартной библиотеки С, входящими в Posix, и эти функции – например, malloc, system, printf, fopen и другие – доступны везде, куда ни сунься.

Это ставит нас перед вопросом: «Какой прок в использовании Posix?» Общеизвестно, что все системные вызовы Posix скопированы в стандарте среды .NET, с использованием управляемых эквивалентов: вы можете читать и записывать файлы, работать со строками, открывать сокеты, читать данные файловой системы и так далее, не беспокоясь о распределении памяти, потому что .NET освободит все, когда сработает сборщик мусора. Но использование версий Posix дает некоторые преимущества:

  1. Унаследованный код очень легко портировать. Вы можете взять код на C и запросто перенести его на C#, затем, при добавлении новых функций, добавить расширенную функциональность, присущую C#.
  2. В том же русле: для C-программистов вполне очевидно, что делает код C# Posix, а это облегчает изучение и сопровождение.
  3. Вы можете использовать преимущества специфичной для Posix функциональности. Например, чтение данных из файла /etc/passwd в обычном .NET коде необходимо делать вручную, а с использованием инструментов Posix это раз плюнуть.

Итак, использование Posix не лишено преимуществ, но вдобавок имеется одно большое неудобство: львиная доля Posix работает с указателями.

«Указатели?» Так и слышу, как вы охнули. «Привет! Говорят 1980-е! Они требуют обратно свой безумный, анахроничный, осложненный переполнениями буфера доступ к памяти!» Именно так. Указатели – это программные имена, описывающие конкретный участок памяти. Например, переменная – указатель на строку содержит точный адрес в памяти, где располагается строка текста. Понятно, что это прекрасно для быстродействия, так как между программой и оборудованием нет посредников, но ужасно с точки зрения безопасности, потому что программа имеет полную власть над вашим компьютером: даже крошечная щелочка в безопасности может вылиться в захват системы. Теперь, когда вы знаете все о плюсах и минусах Posix, давайте нырнем в него и посмотрим, что тут можно сделать...

Базируемся на Stdlib

Имеется три компонента для поддержки Unix в Mono: Mono.Posix, Mono.Unix и Mono.Unix.Native. Два последних отличаются лишь тем, что Mono.Unix – это небольшая обертка для Mono.Unix.Native, но вы можете использовать ту, где вам комфортнее.

Начнем с простого: создадим новое решение под названием Monix, затем изменим его код Main.cs так:

using Mono.Posix;
 using Mono.Unix;
 using Mono.Unix.Native;
 using System;
 using System.Text;
 namespace monix {
    class Monix {
     public static void Main(string[] args) {
       Stdlib.system("ls");
     }
   }
 }

Этот простой код – основа для всех дальнейших: будем изменять только строку Stdlib.system() да добавлять кое-какие кусочки. Проверьте наличие Mono.Posix и добавьте ссылку на него в проект. В нашем первом методе мы воспользуемся классом Stdlib для вызова system(). Класс Stdlib содержит, в основном, статические методы, то есть вам не нужно создавать объект Stdlib для вызова этих методов. Метод system() (следите за регистром s – он нижний: сейчас мы в стране С!) исполняет любую команду на локальной машине, словно он был введен в командной строке. Для нашего примера это означает запуск ls, поэтому программа выведет список каталогов, как если бы вы сами запустили «ls».

После ввода Stdlib.system(), MonoDevelop должна вывести информацию о параметрах метода system(), и вы увидите, что он принимает строки C#. В этом месте разработчики Mono адаптировали библиотеку вызовов C для лучшей совместимости с программированием .NET – обычно, в терминах С, system() получает const char*, так что использование строк более изящно!

Этот переход существует лишь в некоторых методах. Например, printf() также дружественен к .NET, поэтому вы можете писать код вроде этого:

Stdlib.printf("Hello, %s!\n", "world");
 Stdlib.printf(string.Format("Hello, {0}!\n", "world"));

С другой стороны, методы fopen(), fwrite() и fclose() для работы с файлами требуют указателей. В C# указатели известны как IntPtr, потому что это представление указателя в целочисленном типе данных. Эти IntPtr'ы могут восприниматься как данные с неизвестной структурой: их нельзя прочесть без использования специфичных для этих данных методов. Например, файлы открываются так:

IntPtr foo = Stdlib.fopen("file.txt", "w");

Но вы не можете читать или записывать с этого файлового дескриптора без других методов Stdlib. foo IntPtr – всего лишь дескриптор данных, и сам по себе бесполезен. На самом деле, это даже небезопасно: любая память, присвоенная указателю, недоступна сборщику мусора Mono, и необходимо освобождать ее вручную, не то образуется утечка [memory leak]. Вы можете выполнить запись в этот файл, затем закрыть его так:

Stdlib.fwrite(Encoding.ASCII.GetBytes("Hello, world!"), foo);
 Stdlib.fclose(foo);

Развернем обертки

Как указывалось ранее, Mono предоставляет набор упрощенных оберток для базовых структур данных и системных вызовов Unix. Например, любую информацию о пользователе можно прочесть, создав объект UnixUserInfo таким образом:

UnixUserInfo user = new UnixUserInfo("paul");
 Console.WriteLine(user.HomeDirectory);

Класс UnixUserInfo читает информацию из /etc/passwd, и вы можете увидеть имя пользователя, информацию о группах, их командных оболочках и так далее. Подобные структуры существуют и для файловых систем – следующая строка кода выудит информацию о вашем корневом каталоге:

UnixDriveInfo drive = new UnixDriveInfo("/");

Затем вы можете узнать объем свободного пространства на диске, прочитав drive.AvailableFreeSpace. Это число возвращается в байтах, поэтому вы можете пожелать удобства ради преобразовать его в гигабайты:

Console.WriteLine(drive.AvailableFreeSpace / 1024 / 1024 / 1024.0);

Последнее 1024 записано как 1024.0, потому что это заставит Mono преобразовать конечный результат в число с плавающей точкой, а не в целое – в противном случае результат не будет точным!

Иногда эти обертки имеют собственные методы, как в случае с UnixFileInfo – она читает информацию о конкретных файлах, предоставляя вам такие методы, как CanAccess(), но, что более важно, позволяет создавать символьные ссылки на файл путем вызова функции CreateSymbolicLink(), примерно так:

UnixFileInfo file = new UnixFileInfo("file.txt");
 file.CreateSymbolicLink("filesym.txt"

создаст ссылку filesym.txt на file.txt, как если бы вы выполнили ln -s file.txt filesym.txt в командной строке.

Звенит сигнал тревоги

Последний метод, который я хочу показать – signal(), он просто показывает, насколько хорошо интегрированы Mono и библиотека C: вы можете попросить Linux вызвать метод C# при поступлении любого сигнала. «Сигнал» в стране C – это то, что происходит, когда ОС пытается по каким-то причинам прервать программу. Например, нажатие Ctrl+C посылает программе SIGINT, что обычно приводит к выходу. А если вы не хотите, чтобы программа завершалась? Что ж, тогда потрудитесь сообщить C#, как поступать при получении SIGINT, и это делается при помощи метода signal(). Он принимает два параметра: сигнал, который вы хотите перехватить, и имя функции, вызываемой при получении сигнала.

Говоря о SIGINT – вот код, который необходимо ввести в программе, чтобы она не отвечала на Ctrl+C:

Stdlib.signal(Mono.Unix.Native.Signum.SIGINT, HandleSigInt);

HandleSigInt – новый метод, который необходимо создать за пределами Main(). Вот пример:

public static void HandleSigInt(int sig) {
    Console.WriteLine("А я против!\n");
 }

Теперь при нажатии Ctrl+C пользователь получит сообщение-отказ; но это не остановит сигнал SIGKILL (посылаемый, когда кто-то выполняет kill -9 <ваш pid>).

Конечно, вы не сможете протестировать обработку вашей программой сигналов прерывания, пока не заставите ее работать бесконечно:

System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);

На этом наш блиц-тур по интеграции Mono и Unix закончен. Пожалуйста, не забывайте о потенциальных проблемах: утечки памяти – особенно в длительно работающих программах – могут вызвать серьезные осложнения, а привычка работы с Mono способствует небрежному обращению с памятью. Применение родных функций Unix делает миграцию с C на C# быстрой и простой, но в долгосрочной перспективе лучше начать вытеснять функции C-эквивалентами, родными для .NET...

Врезки

Скорая помощь

  • Не пытайтесь проигнорировать сигнал SIGKILL – все равно не удастся: в противном случае некоторые программы никогда не завершались бы!
  • Используя system() и аргументы, переданные вашей функции, вы можете за минуты сколотить быструю оболочку. Начните с пересылки всего переданного в system() и продвигайтесь далее, обрабатывая аргументы и по желанию добавляя собственную функциональность.
Личные инструменты
  • Купить электронную версию
  • Подписаться на бумажную версию