суббота, 26 сентября 2009 г.

Кросс-компиляция C/C++ программ для Windows в Linux

Кратко расскажу как создать так называемый cross-compile toolchain для компиляции Windows-программ из под Linux. Cross-compile toolchain - это набор средств для создания приложений для целевой платформы не имея собственно самой целевой платформы. Обычно это используется при сборке программ под различные экзотические платформы, в данном случае такой целевой платформой выступает далеко не экзотическая Windows. Термин "целевая платформа" уже прозвучал, помимо него имеется еще термин "хост-платформа" (или "платформа сборки"), думаю их значение пояснять не нужно.
Хватит рассуждать пора делом заниматься, привожу краткую инструкцию в результате которой вы получите готовый инструмент для сборки С/С++ программ для Windows непосредственно в Linux. Используя инструкцию собрал toolchain в ArchLinux и CentOS 5.3, после собрал с помощью него MinGW32 для Windows с последним GCC 4.4.1 с поддержкой C/C++ прямо в Linux. В итоге остался очень доволен и получил ценный опыт, которым собираюсь поделиться с вами.


Перед тем как непосредственно приступить убедитесь что у вас установлены средства разработки, такие как GCC, G++, CVS, Automake, Autoconf, make и т.п. Если что-то не сработает в ходе выполнения инструкции - возможно в системе не хватило каких-то пакетов. Если вы занимаетесь разработкой на C/C++ то скорее всего все эти пакеты в вашей системе уже установлены.


Первым делом получим последние версии MinGW Win32 API и MinGW Win32 Runtime:

export CVSROOT=:pserver:anoncvs@cygwin.com:/cvs/src
cvs login (пароль anoncvs)
cvs -z3 co -d mingwrt src/winsup/mingw
cvs -z3 co -d w32api src/winsup/w32api

Теперь скачаем Binutils, GCC, GCC-G++, GMP и MPFR:
wget http://ftp.gnu.org/gnu/binutils/binutils-2.19.1.tar.bz2
wget http://gcc-uk.internet.bs/releases/gcc-4.4.1/gcc-core-4.4.1.tar.bz2
wget http://gcc-uk.internet.bs/releases/gcc-4.4.1/gcc-g++-4.4.1.tar.bz2
wget ftp://ftp.gnu.org/gnu/gmp/gmp-4.3.1.tar.bz2
wget http://www.mpfr.org/mpfr-current/mpfr-2.4.1.tar.bz2

Распакуем все что скачали:
tar xjf binutils-2.19.1.tar.bz2
tar xjf gcc-core-4.4.1.tar.bz2
tar xjf gcc-g++-4.4.1.tar.bz2
tar xjf gmp-4.3.1.tar.bz2
tar xjf mpfr-2.4.1.tar.bz2

Начнем со сборки и установки Binutils для целевой платформы:
cd binutils-2.19.1
mkdir build && cd build
../configure --target=i686-pc-mingw32 --disable-multilib
make
sudo make install
cd ../..

make install установит собранные бинарники в /usr/local/i686-pc-mingw32
Для дальнейшей сборки надо скопировать заголовочные файлы MinGW в папку куда будут устанавливаться файлы для целевой архитектуры:
sudo cp -r mingwrt/include /usr/local/i686-pc-mingw32
sudo cp -r w32api/include /usr/local/i686-pc-mingw32

Если в переменной окружения PATH у вас отсутствует путь /usr/local/bin - добавте его:
export PATH="/usr/local/bin:$PATH"

Теперь соберем и установим GCC с ключом -gcc (предварительно сделаем ссылки на GMP и MPFR, чтобы они тоже собрались):
cd gcc-4.4.1
ln -s ../gmp-4.3.1 gmp
ln -s ../mpfr-2.4.1 mpfr
mkdir build && cd build
../configure --target=i686-pc-mingw32 --disable-multilib
make all-gcc
sudo make install-gcc
cd ../..

Понадобятся библиотеки из MinGW API:
cd w32api
mkdir build && cd build
../configure --host=i686-pc-mingw32 --prefix=/usr/local/i686-pc-mingw32
make
sudo make install
cd ../..

К сожалению тут идет неприятная часть с кросс-зависимостями между GCC и MinGW Runtime, которая, впрочем, решается довольно просто. Мы повторно собираем GCC но уже с ключом all:
cd gcc-4.4.1/build
make

Сборка завершится с ошибкой, но нам это не важно, главное были собраны необходимые библиотеки, которые понадобятся для сборки MinGW Runtime, скопируем их в папку целевой платформы:
sudo cp i686-pc-mingw32/libgcc/crtbegin.o i686-pc-mingw32/libgcc/crtend.o \
i686-pc-mingw32/libgcc/libgcc_eh.a i686-pc-mingw32/libgcc/libgcc.a \
/usr/local/i686-pc-mingw32/lib/
cd ../..

Теперь можно собрать и установить MinGW Runtime:
cd mingwrt
mkdir build && cd build
../configure --host=i686-pc-mingw32 --prefix=/usr/local/i686-pc-mingw32
make
sudo make install
cd ../..

Возвращаемся к сборке GCC, теперь все должно собраться нормально:
cd gcc-4.4.1/build
make
sudo make install
cd ../..

Ну вот и все, cross-compile toolchain готов, можете приступать к сборке нативных Windows-приложений не выходя из Linux. В качестве компилятора для C используйте i686-pc-mingw32-gcc и для C++ используйте i686-pc-mingw32-g++. Для autoconf скриптов configure достаточно указать опции --host=i686-pc-mingw32 и --target=i686-pc-mingw32, тогда скрипт сам определит нужные программы и по возможности попытается собрать весь проект для запуска на целевой платформе, именно так собирается GCC 4.4.1 для запуска в Windows, но об этом в другой раз. Удачных сборок :)

пятница, 24 апреля 2009 г.

Пример работы с OpenGL 3

Знаете за что я люблю программирование? Казалось бы, работа программиста, это же "обезьяний труд", сидишь с утра до вечера, пишешь код проекта, который большей своей частью состоит из угрюмого кода, такого как проверка на ошибки, защита от дурака, работа с памятью. Все это должно угнетать. Однако, когда вот в такой вот рутине появляется возможность изучить что-то новое, прикоснуться к ранее неизвестному и реализовать это. Это по настоящему приятно, это доставляет моральное удовольствие и иногда это даже лучше чем шоколад :)

И к чему я это пишу? Да просто хочу вам сказать, что вышел уже OpenGL 3.1 и пора бы его немного пощупать. К сожалению достойных туториалов по Интернету мало, не говоря уже о туторах на русском, поэтому свой опыт знакомства я решил описать именно в виде небольшого пособия, которое, возможно, кому-нибудь да поможет.

Чем отличается OpenGL 3.0 от более младших версий? Все просто - вся поддержка FFP (fixed function pipeline) переведена в ряд морально устаревших вещей, а точнее помечена deprecated. Это конечно не все, еще часть функций из расширений перекочевала в ядро, но самое важное изменение именно отказ от FFP. Сразу замечу что в OpenGL 3.1 уже убрали все что было помечено deprecated. Осталась одна маленькая лазейка для использования FFP это GL_ARB_compatibility (это расширение доступно только в OpenGL 3.1). Вобщем более детально можно почитать в спецификации, которую можно взять с сайта opengl.org

Чем же грозит отказ от FFP? В первую очередь убран так называемый immediate mode*, это различные функции вроде glVertex, glNormal и т.п. Пользоваться glBegin/glEnd больше нельзя. Убрали glVertexPointer, glNormalPointer и т.п.. Убрали операции с матрицами, т.е. различные glLoadIndentity, glMultMatrix, glLoadMatrix, glOrtho, glFrustum и т.п. Убрали дисплейные списки (display list). И много чего еще. Все эти изменения коснулись также и GLSL, из которого убрали связанные с FFP юниформы (uniform), такие как gl_ModelViewProjectionMatrix, gl_NormalMatrix, gl_Vertex, gl_FogCoord и т.п. Теперь вся ответственность за вычисление подобных вещей возложена на программиста, а это, между прочим, не так уж и плохо ;)

Пожалуй теории на этом хватит, перейдем к некоторым практическим аспектами реализации. Во-первых, нормальной поддержки OpenGL 3.1 в стабильных драйверах ATI и nVidia пока нет, скажу даже больше, пока еще страдает и поддержка OpenGL 3.0. Поэтому будем создавать forward контекст пока что с поддержкой OpenGL 3.0 и будем использовать GLSL 1.3.

Есть интересная особенность при создании forward контекста, для его создания нужно иметь функцию wglCreateContextAttribsARB, которую надо получать через wglGetProcAddress. Ничего не смущает? Правильно, использовать wglGetProcAddress можно только после создания контекста OpenGL. Замкнутый круг :) На самом деле это не проблема, мы просто создаем временный OpenGL контекст, как мы это делаем обычно, получаем адрес функции, создаем уже forward контекст и удаляем временный. Звучит просто? Так оно и есть, вот пример кода:
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL;
int attribs[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 0,
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
0
};

/* create temp render context */
HGLRC temp_render_context = wglCreateContext(window->context);
if (!temp_render_context || !wglMakeCurrent(window->context, temp_render_context))
{
LOG_ERROR("Creating temp render context fail (%d)\n", GetLastError());
destroy_opengl_window(window);
return false;
}

wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");

if (NULL == wglCreateContextAttribsARB)
{
LOG_ERROR("%s fail\n", "wglCreateContextAttribsARB");
wglDeleteContext(temp_render_context);
destroy_opengl_window(window);
return false;
}

/* create forward render context */
window->render_context = wglCreateContextAttribsARB(window->context, 0, attribs);
if (!window->render_context || !wglMakeCurrent(window->context, window->render_context))
{
LOG_ERROR("Creating render context fail (%d)\n", GetLastError());
wglDeleteContext(temp_render_context);
destroy_opengl_window(window);
return false;
}

/* delete temp render context */
wglDeleteContext(temp_render_context);
Если сейчас что-то непонятно - не расстраивайтесь, сначала просмотрите всю статью целиком, посмотрите на исходные тексты к этой статье, ссылка на которые приведена в самом конце.

В качестве тестовой сцены я решил сделать обычный вращающийся куб с натянутой на него текстурой и диффузным повершинным (per vertex) освещением. Данные о вершинах куба и индексы просто забиты руками прямо в код программы, никакой загрузки моделей для этого проекта я делать не стал. Все что загружается извне это код шейдеров и текстура куба (которая представлена в формате TGA).

Ну что же, мы создали forward контекст... И что дальше? Нету ни glMatrixMode ни gluPerspective, как установить матрицу перспективного преобразования? В forward контексте - только руками. Например функция, которая сформирует проекционную матрицу аналогичную той, что формирует gluPerspective:
static inline void matrix4_projection(matrix4 M, float fov, float aspect, float znear, float zfar)
{
float f = 1.0f / tanf(fov/2.0f);
memset(M, 0, sizeof(float)*16);
M[0][0] = f/aspect;
M[1][1] = f;
M[2][2] = (zfar+znear)/(znear-zfar);
M[2][3] = 2*zfar*znear/(znear-zfar);
M[3][2] = -1.0f;
}
Здесь matrix4 это typedef float matrix4[4][4], все очень просто. Ну а дальше вы берете эту матрицу и отправляете ее в шейдер, вместе с видовой матрицей, делается это вызовом функции glUniformMatrix4fv. Более конкретно это показано в исходниках.

Для вывода самого куба необходимо передать в шейдер данные о его вершинах: позицию в пространстве, нормаль и текстурные координаты для каждой вершины. Для этого в шейдере заводится несколько атрибутов, которые обозначаются с помощью квалификатора in (но вообще говоря если никакого квалификатора не указано, то подразумевается что это in). До GLSL 1.3, на то, что это атрибут указывал квалификатор attribute. Работа с атрибутами внутри программы не поменялась, после загрузки и подготовки шейдеров надо получить позицию (location) атрибута, выбрать активную шейдерную программу и передать в атрибут необходимые значения. Также выглядит и работа с юниформами (uniform). Вот код реализующий передачу данных о вершинах куба в шейдер:
position_index   = get_attrib_index(&shader, "position");
normal_index = get_attrib_index(&shader, "normal");
texcoord_index = get_attrib_index(&shader, "texcoord");

glEnableVertexAttribArray(position_index);
glEnableVertexAttribArray(normal_index);
glEnableVertexAttribArray(texcoord_index);

use_shader_program(&shader);

glVertexAttribPointer(position_index, 3, GL_FLOAT, 0, 0, cube_vertices);
glVertexAttribPointer(normal_index, 3, GL_FLOAT, 0, 0, cube_normals);
glVertexAttribPointer(texcoord_index, 2, GL_FLOAT, 0, 0, cube_texcoords);
За более подробной информацией по функциям get_attrib_index и use_shader_program отсылаю вас к исходникам тествого проекта, коротко говоря это просто обертки над glGetAttribLocation и glUseProgram, соответственно.

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

Ссылка на архив с материалами к данной статье (включает уже собранный проект, текстуру и шейдеры и исходники, а также Makefile для MinGW): gl99.7z (220 KB).

* Мне уже указали на то, что immediate mode можно вполне использовать и без FFP, но это не меняет того факта, что этот функционал был объявлен deprecated в OpenGL 3.0

четверг, 5 февраля 2009 г.

wget для windows

Выкладываю сборку wget для Windows с поддержкой SSL (использована библиотека openssl-0.9.8j): wget для windows

Не знаю как вам, а мне программа под названием wget очень нравится, я часто ей пользуюсь когда работаю на каком-нибудь *nix сервере через удаленный шелл (впрочем, альтернатив этой утилите там практически нету). Однако в среде Windows эту программу мало кто знает и использует. Тем не менее, существует вполне официальный способ собрать исполняемый файл под Windows и использовать все возможности этой программы.

Для тех кто не помнит wget - это консольная "качалка" файлов из интернета. Консольная значит что у нее нету графического интерфейса, общаться с программой можно через ключи командной строки. Существуют также сторонние программы предоставлющие грфический интерфейс к этой утилите, однако здесь мы их рассматривать не будем.

Возможностей у программы много: она может качать через HTTP, HTTPS, FTP; может работать через прокси; может ограничивать скорость закачки; может докачивать файлы в случае обрыва связи; может работать со списками закачек; может скачивать не только файлы, но и целые папки, причем делать это рекурсивно (т.е. скачивая все вложенные папки и файлы). Кстати wget можно использовать как "граббер" сайтов (скачивать сайт подменяя в нем ссылки на локальные файлы). Поистине мощная программа.

четверг, 29 января 2009 г.

Множество Мандельброта

На днях написал небольшую программу на Си, которая строит множество Мандельброта и сохраняет его в BMP-файл. Картинка получается большая (4096х4096), но имеет всего 16 цветов и поэтому весит порядка 8 МБ. А вот и сам код:
#include<stdio.h>/* this code draw Mandelbrot set and save it into BMP file */
unsigned long h[]={7753026UL,128L,7733248UL,2621440UL,268435456UL,268435456UL,
65536L,4L,0L,0L,0L,1048576L,0L,0L,65280L,336920320UL,673775380UL,1010630440UL,
1347485500UL,1684340560UL,2021195620UL,2358050680UL,2694905740UL,3031760800UL,
3368615860UL,3705470920UL,4042325980UL,4210753520UL,4294967290UL,4294967295UL}
;int main(int n,char**f){FILE*o=fopen("fractal.bmp","wb");if(NULL==o)return 1;
;fwrite(h,1L,118L,o);**f=0;int c,x,y;for(y=-2048;y<2048;++y)for(n=0,x=-3000;x<
1096;n=00,++x){float i,j,k;i=j=k=0;while(i*i+j*j<4&&n++<16){i=i*i-j*j+x/1600.0
;j=2*k*j+y/1600.0;k=i;}c=**f?fputc((c<<4)|(n&15),o):n&15;**f=!**f;}fclose(o);}

А вот такая получается картинка (уменьшенная копия):

четверг, 22 января 2009 г.

Hello, World!

Читая стандарт C99 я открыл для себя много нового и интересного. В частности мне понравились удивительные синтаксические конструкции которые можно создавать используя информацию из стандарта C99.

Приведу пример двух программ которые печатают "Hello, World!" на экран:
%:include<stdio.h>
main()<%char*a<::>=<%"Hello,World!",0%>;puts(a<:(int)a<:1:>:>);%>

#include<stdio.h>
int main()
{
puts((char<::>)<%<:0x8:>=0x6F,<:0xA:>=0x6C,<:0x9:>=0x72,<:0x0:>=0x48,
<:0x6:>=0x20,<:0x2:>=0x6C,0x6C,<:0x4:>=0x6F,<:0x1:>=0x65,<:0x5:>=0x2C,
<:0xB:>=0x64,<:0x7:>=0x57,<:0xC:>=0x21,0x00%>);
}

В обоих примерах используются так называемые диграфы, это пара символов призванная заменить один, их использование вполне допустимо в языке Си и тесно связано с его происхождением. Когда язык Си появился на свет, имелись клавиатуры на которых отсутствовали символы '{', '}', '[', ']', '#' и бог его знает какие еще. Но, как известно, эти символы широко используются в языке Си и, чтобы не ущемлять права владельцев подобных "клав" в стандарт были введены диграфы, которые позволяли заменить эти символы на пару других, например '{' и '}' заменяются на '<%' и '%>', а '[' и ']' заменяются на '<:' и ':>', а также '#' заменяется на '%:'. Кончено, в наше время это кажется странным, но из стандарта диграфы так и не ушли.

И если с первым примером все становится более-менее ясно, то вот второй, даже после "исправления" диграфов, кажется странным. Тут все дело в стандарте C99. Дело в том, что там позволяется делать интересные вещи: объявлять безымянные массивы и посылать их в функцию, а также явно задавать положение элементов в объявленном массиве. Т.е. конструкция (char[]){1, 2, 3, 4, 5} есть не что иное как массив, тот же самый результат даст конструкция (char[]){[1]=2, [0]=1, [2]=3, 4, 5}. Вот теперь все должно встать на свои места :)

Удачного кодинга!