Первая программа под Plan 9
Как и полагается первая программа на Си это Hello World, вот версия этого шедевра для Plan 9:
#include <u.h>
#include <libc.h>
void main(int, char**)
{
print("Hello world");
exits(0);
}
Самая первая строка подключает заголовочный файл u.h, который содержит определения типов данных. Файл u.h является одной из аппаратно зависимых частей, поэтому находится в каталоге /386/include/ (для x86 архитектуры). Файл libc.h содержит набор «стандартных» процедур, находится в /sys/include. Выполнение программы начинается из функции main, печать выполняется функцией print которая является аналогом printf в *nix системах. Выход из программы осуществляется по exits(char *msg), параметр msg указывает на строку которая описывает причину завершения программы, или 0 в случае успешного завершения:
exits("программа выполнила невыполнимое");
Статус возвратится в переменной окружения $status (широко используется для скриптов), которую вы можете просмотреть командой:
% echo $status
8.out 269: программа выполнила невыполнимое
%
Если вы выполнили программу из под Acme то статус отобразится в окне Errors автоматически. Также необходимо заметить, что текст обрезается до величины ERRLEN. А теперь модифицируем программу для обработки параметров переданных командной строкой, для этого есть несколько полезных макросов:
#include
#include
void main(int argc, char *argv[])
{
int i,n=1;
char *name="World";
ARGBEGIN {
case 'i':
n=atoi(ARGF());
break;
case 'n':
name=ARGF();
break;
default:
print("Usage %s: [-i count] [-n name]\n");
exits(0);
} ARGEND
for (i=0;i<n;i++)
print("Hello %s!\n",name);
exits(0);
}
Если вы захотите узнать, как работают эти макросы, то искать их нужно в /sys/include/libc.h. Система поддерживает Unicode, следовательно, вы можете использовать любой доступный язык, при использовании строк типа char используется представление UTF-8, для использования представления UTF-16 необходимо использовать тип Rune, который определен как unsigned short. Для прояснения ситуации с представлением UTF-8 скомпилируйте следующую программу:
#include
#include
int useutf=0;
void main(int argc, char *argv[])
{
char *name=nil;
int n;
ARGBEGIN {
case 'u':
useutf++;
break;
case 'n':
name=ARGF();
break;
} ARGEND
if (name!=nil)
{
if (useutf)
n=utflen(name);
else
n=strlen(name);
print("Your name length: %d\n",n);
exits(0);
}
print("usage: %s -u
Программа принимает два параметра, n - обязательный, например ваше имя, u - указывает как следует "понимать" строку, как UTF или как ASCII. Вот какой результат мы получим:
8.utfwarn -n Sergey
Your name length: 6
8.utfwarn -n Sergey
Your name length: 6
8.utfwarn -n Сергей
Your name length: 12
8.utfwarn -u -n Сергей
Your name length: 6
Если вы хотите узнать количество символов, то используйте utflen, а если количество байт памяти которое она занимает то strlen.
Теперь давайте посмотрим как обстоят дела с графикой.
Для использования (для упрощения использования) графических возможностей системы вам необходимо подключить библиотеку libdraw, как это сделано в следующей программе:
#include <u.h>
#include <libc.h>
#include <draw.h>
void main(int argc,char **argv)
{
Rectangle r;
initdraw(0,0,"Sample 2");
r=screen->r;
border(screen,r,1,display->black,ZP);
r.min.x+=5;
r.max.y+=5;
string(screen,r.min,display->black,ZP,font,"Hello world");
flushimage(display,1);
for(;;);
exits(0);
}
Для инициализации графики необходимо вызвать initdraw, которая инициализирует ряд глобальных переменных (предопределенных в библиотеке):
display - Представляет подключение к графическому драйверу, содержит ссылки на все ресурсы необходимые для работы с графикой.
screen - Из структуры display, для простоты использования вынесена в отдельную переменную, представляет собой изображение окна, над которым и выполняются все операции.
font - Тоже из структуры display, используемый по умолчанию шрифт.
Вот краткое описание использованных функций:
border - рисует прямоугольник заданной толщины. Первый параметр указывает где будет нарисован прямоугольник, вторый – размер и положение. Следующий параметр изображение, которое будет использоваться как текстура. Для наложения просто цвета достаточно создать изображение размеров 1х1 пиксель. Последний параметр указывает, с какой точки текстуры начнется копирование пикселей, для указания координат 0:0 достаточно использовать предопределенную точку с именем ZP.
string - рисует строку в указанной точке, указанным шрифтом, строка должна завершатся 0, иначе необходимо использовать stringn и указать количество символов для печати. Для печати unicode символов следует использовать runestring. Также следует заметить что символы типа \n игнорируются что в ряде случаев совсем не к стати. Функция возвращает точку на которой закончилась печать.
flushdisplay - для того чтобы удостоверится, что изменения отобразятся на экране, в случае если операции буферизировались
Дальше программа просто „виснет”, здесь не предусмотрен случай изменения размеров окна, полностью игнорируются мышь и клавиатура. Для того чтобы устранить эти недостатки необходимо использовать механизм обработки событий, который предоставляется библиотекой libdraw, как это сделано в следующем примере:
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
char *str[]={"Exit",0};
Menu menu={str,0,0};
int cx,cy;
Image *img;
void RedrawScreen(void)
{
Rectangle r;
r=screen->r;
r.max.x=r.min.x+cx;
r.max.y=r.min.y+cy;
draw(screen,r,img,display->opaque,ZP);
}
void eresized(int new)
{
if(getwindow(display, Refnone) < 0)
sysfatal("resize failed: %r");
RedrawScreen();
}
void main(int argc,char **argv)
{
Event e;
int type;
initdraw(0,0,"Sample 4");
einit(Emouse|Ekeyboard);
cx=screen->r.max.x-screen->r.min.x;
cy=screen->r.max.y-screen->r.min.y;
img=allocimage(display,Rect(0,0,cx,cy),RGB24,1,DWhite);
RedrawScreen();
for(;;)
{
type=event(&e);
switch(type)
{
case Emouse:
switch(e.mouse.buttons)
{
case 1:
e.mouse.xy.x-=screen->r.min.x;
e.mouse.xy.y-=screen->r.min.y;
draw(img,Rect(e.mouse.xy.x,e.mouse.xy.y,e.mouse.xy.x+1,e.mouse.xy.y+1),display->black,display->opaque,ZP);
RedrawScreen();
break;
case 4:
e.mouse.xy.x-=screen->r.min.x;
e.mouse.xy.y-=screen->r.min.y;
draw(img,Rect(e.mouse.xy.x,e.mouse.xy.y,e.mouse.xy.x+5,e.mouse.xy.y+5),display->white,display->opaque,ZP);
RedrawScreen();
break;
}
break;
case Ekeyboard:
if (e.kbdc=='\n')
goto exit;
break;
}
}
exit:
freeimage(img);
exits(0);
}
Для инициализации этого механизма необходимо выполнить einit, указав какие события мы хотим обрабатывать. Далее просто читаем сообщения с помощью функции event, эта функция возвращает индекс сообщения, а структура Event заполняется соответствующими параметрами. Обработка событий предельно проста, для идентификации нажатой клавиши используется переменная kbdс (тип int, кодировка Unicode), а для мыши параметры сохраняются в отдельной структуре с именем mouse, где имеется информация о нажатой клавише buttons (1 - левая кнопка, 2 - средняя, 4 - правая, 8 - колесо вверх, 16 - колесо вниз) и текущее положение мыши xy (тип Point). Для того чтобы приложение могло реагировать на изменение размеров окна пользователь должен определить функцию eresized и там прописать соответствующий код, который обновит переменную display, при изменении размеров окна. Сообщения от мыши и клавиатуры можно читать по отдельности, используя функции emouse и ekbd соответственно. Также эта библиотека позволяет запускать таймер посредством etimer, пример смотрите ниже.
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
int cur_str=0;
char *str[]={"Hello world with graphics","Event library used","It's very simple"};
void RedrawScreen(void)
{
Rectangle r;
r=screen->r;
draw(screen,r,display->white,display->opaque,ZP);
border(screen,r,1,display->black,ZP);
r.min.x+=5;
r.max.y+=5;
string(screen,r.min,display->black,ZP,font,str[cur_str]);
}
void eresized(int new)
{
if(getwindow(display, Refnone) < 0)
sysfatal("resize failed: %r");
RedrawScreen();
}
void main(int argc,char **argv)
{
Event e;
int timer,type;
initdraw(0,0,"Sample 3");
einit(Emouse|Ekeyboard);
timer=etimer(0,5000);
RedrawScreen();
for(;;)
{
type=event(&e);
switch(type)
{
case Emouse:
if (e.mouse.buttons & 1)
{
do {
e.mouse=emouse();
}
while(e.mouse.buttons!=0);
exits(0);
}
break;
case Ekeyboard:
if (e.kbdc=='\n')
exits(0);
break;
default:
if (timer==type)
if (++cur_str==3)
cur_str=0;
RedrawScreen();
break;
}
}
}
Механизм обработки событий упрощает написание программ, но эффективен лишь в однопоточных приложениях, в случае использования библиотеки libthread, ввиду доступности такого средства коммуникации как Канал (Channel), более эффективно (и естественно) использование других видов обработки событий.
Что еще Plan 9 предоставляет программисту? Для того чтобы ответить на этот вопрос достаточно посмотреть intro(2), там вы можете найти краткое описание некоторых библиотек доступных для Plan 9, из них особый интерес представляют libframe для организации пользовательского интерфейса (наподобие Acme), libthread - для создания многопоточных программ, libndb - для получения информации о сетевой конфигурации и.т.д
Редакция от 13.05.05
Copyright 2005 Рева Сергей