LXF98:Mono
|
|
|
Содержание |
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 дает некоторые преимущества:
- Унаследованный код очень легко портировать. Вы можете взять код на C и запросто перенести его на C#, затем, при добавлении новых функций, добавить расширенную функциональность, присущую C#.
- В том же русле: для C-программистов вполне очевидно, что делает код C# Posix, а это облегчает изучение и сопровождение.
- Вы можете использовать преимущества специфичной для 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...