VR-online JOURNAL Фленов Михаил & VR-Team VR-online для программистов №2 Delphi: Динамические массивы....................................................................................................3 Delphi: Воровство паролей ...........................................................................................................7 WinSock: Основные функции.....................................................................................................10 WinSock: Первая сетевая программа .........................................................................................17 OpenGL: Третье измерение и тест глубины..............................................................................21 Форматы файлов: Алгоритм сжатия для GIF ...........................................................................24 Геймер: Введение в DirectDraw..................................................................................................27 Геймер: Программирование с помощью DirectDraw ...............................................................33 SQL: Введение .............................................................................................................................36
Copyright: VR-online Journal http://www.vr-online.ru
VR-online Journal (Фленов Михаил & VR-Team)
Журнал постепенно расширяется, и в нём постоянно будут появляться новые разделы с описанием новых технологий. Ну а самое главное, что у VR-online появляется множество друзей и постоянных читателей, что не может не радовать. Я надеюсь на тебя, что и ты будешь писать для журнала статьи. Если тебе нравится то, что мы делаем, и ты можешь поделиться какими-нибудь знаниями или опытом, то можешь писать нам. Статьи принимаются по адресу vr_online@cydsoft.com. Я обязательно прочту все статьи и включу в очередной номер журнала. Если у тебя есть вопросы или предложения, то заходи лучше на форум на нашем сайте. Ответить вовремя на все вопросы, приходящие по почте я не в состоянии, потому что в нём скапливается по 100-200 писем и чтобы всё это просмотреть и каждому хоть что-то ответить мне приходится выделять чистых 2 дня в неделю. Иногда это не удаётся и ответ может прийти через две или три недели. Я конечно же отвечаю всем, хоть и с небольшими задержками, но ответ может идти долго. А теперь переходим к самому номеру. Я постарался сделать его как можно разнообразней и интересней. Надеюсь, что ты найдёшь для себя что-то новое и интересное. Фленов Михаил aka Horrific
Для программистов №2
VR-online JOURNAL Horrific aka Фленов Михаил
INFO: ИДЕЯ И РЕАЛИЗАЦИЯ: Флёнов Михаил (Horrific) ГРАФИКА: Фленов Михаил, tr4sh VR-Team: Crazy_Script, Del, Demogorgon, Fighter, Mish!, Negus, Spider NE, tr4sh, Ramzes, Shy Hulud INTERNET: WWW: http://www.vr-online.ru E-MAIL: vr_online@cydsoft.com ДИЗАЙН САЙТА: tr4sh КОДИНГ САЙТА: Mish!
Данный журнал распространяется в виде PDF файлов. Вы можете выкладывать номера на любые носители без изменения внешнего вида журнала, без перевода в другие форматы, без изменения самого файла. В журнал запрещается вносить изменения. Перепечатка материалов запрещена. Журнал распространяется бесплатно, и ты можешь скачать его с нашего сайта, поэтому мы не смысла в перепечатывании видим материалов. Если ты хочешь стать автором журнала, то присылай свою статью на наш e-mail и мы обязательно включим её в очередной номер.
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Delphi: Динамические массивы Мы снова возвращаемся к Delphi. Я уже рассказал тебе достаточно много начальных знаний, поэтому я больше не буду размусоливать каждую строчку. Начиная с этого номера, я буду давать тебе пример и объяснять, что в нём происходит. Забирай исходники в файле delphi.zip, и давай посмотрим, что происходит внутри.
Рис 1. Форма Цель это примера научить тебя работать с динамическими массивами, ты будешь встречаться с ними постоянно, поэтому я решил уделить этому внимание уже сейчас. Рассмотрим первую процедуру: var r:array of integer; i:Integer; begin ListBox1.Items.Clear; SetLength(r,10); for i:=0 to High(r) do begin r[i]:=i*i; ListBox1.Items.Add(IntToStr(i)+' в квадрате ='+IntToStr(r[i])); end; end;
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Эта процедура вызывается по нажатии первой кнопки. В области объявлений VAR я объявил две переменные. Первая это r которая является массивом чисел типа Integer. Вторая i это переменная, которую я буду использовать в качестве счётчика. Переходим к самой процедуре: Первая строка очищает все строки у ListBox1. Для этого вызывается процедура ListBox1.Items.Clear. Объясню подробней. У ListBox1 есть свойство Items, где хранятся все строки. У Items есть метод Clear, который удаляет все находящиеся в нём строки. Во второй строке вызывается процедура SetLength, которая выделила память для массива r (первый параметр), размером в 10 элементов (второй параметр). Обращение к элементом будет происходить как r[номер_элемента]. Элементы будут нумероваться от 0 до 9. Вообще, в программировании всё нумеруется с нуля. Далее используется конструкция: for выражение1 to выражение2 do begin end; Эта конструкция создаёт цикл выполняя операторы между Begin и End, "Выражение2" "Выражение1" количество раз. Функция High(r) возвращает количество элементов в массиве r. В итоге получается, что цикл будет выполняться от i:=0 (от нуля), до количества элементов в массиве r (до 9). Внутри массива выполняется две строки: r[i]:=i*i . Здесь i-му элементу массива присваивается i*i. ListBox1.Items.Add(IntToStr(i)+' в квадрате ='+IntToStr(r[i])); Эта строка добавляет новый элемент в ListBox1. Функция IntToStr переводит число в строку. С первой процедурой мы разобрались, теперь перейдём ко второй: type TDynArr=array of integer; var r:TDynArr; i:Integer; begin ListBox1.Items.Clear; SetLength(r,10); for i:=0 to High(r) do r[i]:=i*i; SetLength(r,20); for i:=10 to High(r) do r[i]:=i*i; for i:=0 to High(r) do ListBox1.Items.Add(IntToStr(i)+' в квадрате ='+IntToStr(r[i])); end; Эта процедура выполняет похожие действия, но с небольшими особенностями. В начале я объявляю новый тип: TDynArr=array of integer . После этого конструкция r:TDynArr; будет означать, что r относится к типу TDynArr, а тот относится к array of integer; . Это тоже самое, что мы писали в первой процедуре r:array of integer; , только такая конструкция удобней, если ты захочешь объявить несколько динамических
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
массивов. Тебе не приходится сто раз писать громоздкую строку r:array of integer; , ты объявляешь новый массив как TDynArr. Далее идёт всё та же очистка строк и выделение памяти под массив. for i:=0 to High(r) do r[i]:=i*i; Эта конструкция заполняет десять элементов квадратами числа i. После этого я снова вызываю функцию SetLength(r,20);, в которой говорю, что массив теперь будет состоять из 20-и элементов. Таким способом можно как увеличивать количество элементов, так и уменьшать. for i:=10 to High(r) do r[i]:=i*i; Здесь я заполняю квадратами числа i элементы начиная с 10 по последний. И в конце я снова заполняю ListBox1 значениями элементов массива. Теперь о том, как я изменил иконку для программы. Для того, чтобы изменить иконку для формы нужно дважды щёлкнуть в Object Inspector по свойству Icon и выбрать любую. Для изменения иконки программы нужно щёлкнуть по меню Project и выбрать пункт Option . В появившемся окне выбрать закладку Application и на этой странице нажать кнопку Load Icon . Исходники находятся в файле delphi.zip Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
5
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Тебя мучает одна проблема, которую ты не можешь решить? Заходи на форум на нашем сайте, подумаем вместе!!! На нашем форуме ты можешь задать любой вопрос и получить ответ по следующим темам: • Программирование (Delphi, JBuilder, C++ Builder, Kylix, Visual C++Visual Basic и другие); • Технологии программирования (сети, мультимедиа, DirectX, OpenGL); • Администрирование; • Операционные системы; • Базы данных (SQL Server, язык SQL и другие); • Железо; • Internet технологии (Perl, PHP, Flash, XML); • Софт; • Сети; Адрес сайта http://www.vr-online.ru
http://www.vr-online.ru
6
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Delphi: Воровство паролей По многочисленным заявкам, я сегодня решил рассмотреть взлом с помощью Delphi. Ты узнаешь, как вытащить из системы пароли. На рисунке 1 ты можешь увидеть пример выполнения проги. Правда, паролей ты не увидишь, а то вдруг треснешь от такой халявы. Как и в предыдущей статье, ты должен взять пример в файле pswhack.zip, а я сейчас расскажу, как всё реализовано.
Рис 1. Форма В обработчике события формы "OnCreate" вызывается недокументированная функция WNetEnumCachedPasswords . Эта функция ищет пароли в кэше и возвращает их в процедуру указанную в качестве четвёртого параметра. В Win 9x есть огромный баг при запуске widows пароли загружаются в память и хранятся там, на протяжении всего сеанса, поэтому их легко выдернуть с помощью этой функции. Самое интересное, что они хранятся в незашифрованном виде, и тебе не придётся ничего расшифровывать. Теперь посмотрим как объявлена эта функция. Объявление состоит из двух строк. В первой я просто описал, что она представляет из себя. Вторую я рассмотрю подробнее: function WnetEnumCachedPasswords // Имя функции (lp: lpStr; //Должен быть NIL w: Word; // Должен быть 0 b: Byte; // Должен быть $FF PC: PChar; // Адрес функции, в которую вернутся пароли dw: DWord): Word; // опять 0 external mpr // Имя DLL файла где находится эта функция name 'WNetEnumCachedPasswords';//Имя функции в DLL файле. Теперь ты разберёшься и с первой строкой описания: function WnetEnumCachedPasswords (lp: lpStr; w: Word; b: Byte; PC: PChar;
http://www.vr-online.ru
7
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
dw: Dword ): Word; stdcall; Единственное, чего ты можешь не знать, так это слово "stdcall". Оно означает, что функция использует стандартный вызов. Функция, в которую возвратятся пароли, должна выглядеть как: function AddPassword ( WinPassword: PWinPassword; dw: Dword ): LongBool; stdcall;
//Имя функции, может быть любым. //Указатель на структуру WinPassword //Мы не будем использовать.
Теперь нужно знать, что такое WinPassword . Эта не стандартная структура и её объявления ты нигде не найдёшь, поэтому ты должен объявить её ручками: type PWinPassword = ^TWinPassword; TWinPassword = record EntrySize: Word; ResourceSize: Word; PasswordSize: Word; EntryIndex: Byte; EntryType: Byte; PasswordC: Char; end; В "PasswordC" будет находится строка содержащая имя пользователя и пароль. ResourceSize - размер имени пользователя, а PasswordSize - размер пароля. Дальше я не буду ничего объяснять. Если сможешь, то разберешься сам, а если нет, то сочувствую. Единственное, что я могу сказать, так это то, что пароль хранится в DOS кодировке. Поэтому, чтобы его увидеть, надо перекодировать в Windows-кодировку. Для этого я использую функцию CharToOem. В качестве первого параметра передаётся то, что надо перекодировать, а второй - результат перекодировки. На этом всё. Это занятие оказалось маленьким, но поучительным. Исходники находятся в файле pswhack.zip Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
8
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Реклама в журнале VR-online Почему вы обязаны разместить рекламу на страницах VRonline:
4. 5. 6. 7.
1. Таких низких цен вы не видели ни где. 2. У нас располагается нестареющая информация. Которая будет актуальна всегда. 3. Вы имеете возможность пожизненно расположить свой банер на наших страницах по самым низким ценам. Пожизненность гарантируется в не зависимости от роста числа посещаемости. У нас есть потенциал для роста, как в объемах страниц, так и в посещаемости. Наши материалы очень часто сохраняются на дисках посетителей. Ваша реклама будет доступна в любых вариантах журнала. Журнал распространяется не только с сайта VR-online, но и другими сайтами и даже на CD, поэтому тираж огромен.
Если вы собираетесь рекламировать не просто сайт в интернете, а компанию, которая занимается информационными технологиями, то ваша дорога лежит сюда. Это лучшее рекламное место, которое можно найти в сети. Торопитесь такие цены не надолго. Расценки на размещение рекламы на страницах VR-online: • Банер 100х100 на главной странице сайта в течении месяца-$25 • Банер 468х60 на главной странице сайта в течении месяца-$30 • Банер 100х100 на странице оглавления на сайте 1-го номера (пожизненно)-$25 • Банер 468х60 на странице оглавления на сайте 1-го номера (пожизненно)-$30 • Страница в журнале-$200 • Половина страницы в журнале-$100 • Банер на странице статьи журнала.-$50
http://www.vr-online.ru
9
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
WinSock: Основные функции Библиотека Winsock.dll, как и библиотеки UNIX систем, основана на реализации нескольких функций для работы с гнёздами.
Первая функция WSAStartup. Она начинает процесс работы с библиотекой WinSock.dll. int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData ); wVersionRequested -Старший байт определяет минимальную версию WinSock.dll, которую вы сможете использовать, а младший - старшую версию. lpWSAData - указатель на WSAData, которая получить дополнительную информацию. Эта функция должна вызываться первой при работе с гнёздами. Ну, здесь сложно чтото говорить, просто давай рассмотрим примерчик, скажу только, что функция эта возвращает код ошибки: WORD wVersionRequested; WSADATA wsaData; int err; // Здесь создаётся запрос на возможные библиотеки от // нулевой версии до второй wVersionRequested = MAKEWORD( 2, 0 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { // Если функция вернула нулик, то ошибок нет, так // что здесь можешь обрадывать пользователя. return; } // // // // if
Теперь проверим, что нам записали в WsaData Если wVersion не равны 0 или 2 то значит система использует более новую версию и придётся закрывать программку ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 0 ) { // сообщи пользователю, что у него нет нужной Winsock.dll // и закрывай программку WSACleanup( ); // Это нужно, чтобы закончить работу return; }
http://www.vr-online.ru
10
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Вторая функция, которую мы рассмотрим, будет socket. Она устанавливает оконечную точку линии связи. SOCKET socket (int af, int type, int protocol); аf - обозначает домен. Возможны следующие варианты данного параметра: AF_UNSPEC - непредусмотренный; AF_UNIX - "UNIX System" канальная связь локальной машины с сервером; AF_INET - интернет протоколы TCP, UDP и так далее. Полный список возможных параметров вы можете найти в winsock.h или winsock2.h, но должен заметить, что AF_UNSPEC, продолжает включатся в заголовочные файлы, но в самой функции не используется и не влияет на её работу, поэтому не рекомендую её использовать. type - тип связи через гнездо (виртуальный канал или дейтаграмма). Возможны только два значения этого параметра: SOCK_STREAM - используется TCP для адресов семейства Интернет, виртуальный канал; SOCK_DGRAM - используется UDP для адресов семейства Интернет, дейтаграмма; Protocol - ну протокол он и в Африке протокол, поэтому возможные варианты смотри в заголовочных файлах. И наконец, функция возвращает значение типа SOCKET, это у нас будет дескриптор гнезда. Засунь это значение куда-нибудь, оно нам ещё пригодится, только сначала проверь, не равно ли это значение SOCKET_ERROR. Если так, то произошла ошибка, а код ошибки ты можешь узнать функцией WSAGetLastError. Например, код ошибки равный WSANOTINITIALISED означает, что ты должен выполнить сначала WSAStartup (об этом чуть позже), или WSAENETDOWN означает, что твоя сеть грохнулась.
Теперь третья интересная функция, которая пригодиться тебе для программирования гнёзд - bind. Эта функция связывает дескриптор гнезда с именем: int bind (SOCKET s, const struct sockaddr FAR* name, int namelen); s - дескриптор гнезда. Помнишь, что тебе вернула наша первая функция socket, теперь вспомни, куда ты это засунул, так как тот дескриптор гнезда нужно вставлять сюда. name - адрес структуры, определяющей идентификатор, характерный для данной комбинации домена и протокола в функции socket (вот это я ляпнул). Короче это структура типа такой: struct sockaddr { u_short sa_family; char sa_data[14]; }; namelen - длина структуры name; без этого параметра ядро не знало бы, какова длина структуры, поскольку она может различаться, в зависимости от доменов и протоколов.
http://www.vr-online.ru
11
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Ну и что же нам возвращает эта функция? А возвращает она код ошибки. Если он равен нулю, то можешь прыгать до потолка, а если нет, то ты увидишь SOCKET_ERROR. А код ошибки ты можешь узнать всё той же функцией WSAGetLastError. Ошибки примерно те же самые как и в функции socket.
Следующая функция будет connect. Эта функция отправляет запрос на подключение к существующему гнезду. Эта функция вызывается на машине клиента, для связи с сервером: int connect ( SOCKET s, const struct sockaddr FAR* name, int namelen );
Я думаю, хватит расписывать все функции. Как ты заметил, в их реализации много общего, а именно структура SOCKET и возвращаемое значение, поэтому их описание я буду опускать, а перейдём сразу к name. Name - указывает на выходное гнездо, которое образует противоположный конец линии связи Namelen - Длинна параметра Name Оба гнезда должны использовать одни и те же домен и протокол связи, в таком случае ядро возвращает правильность установки линии связи. Если вы использовали для инициализации гнезда виртуальный канал, то произойдёт соединение двух гнёзд, а если тип гнезда дейтаграмма, сообщаемый функцией connect адрес будет использоваться в последующих обращениях к функции send через данное гнездо, в этом случае в момент вызова никаких соединений не производится.
Следующая функция Listen вызывается на машине сервере. Эта функция служит для начала прослушивания гнезда на случай подключения к нему со стороны клиента: int listen (SOCKET s, int backlog); s - дескриптор гнезда. backlog - максимально-допустимое число запросов, ожидающих обработки. Если этот параметр равен SOMAXCONN, то ядро само установит максимальное значение. В большинстве случаев, параметр blocklog зависит от установленного в системе параметра "максимальное количество подключений". Если вы используете Windows 95/98, то этот параметр регулируется в настройках сети.
Ну и на конец для подтверждения сервером соединения необходимо вызвать accept. Эта функция принимает запросы на подключение, поступающие на вход процессасервера:
http://www.vr-online.ru
12
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
SOCKET accept ( SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen ); s - дескриптор гнезда addr - указатель на структуру, в котором ядро возвращает адрес подключаемого клиента addrlen - размер пользовательского массива По завершении выполнения функции ядро записывает в переменную addrlen длину параметра addr. Функция возвращает новый дескриптор гнезда, отличный от дескриптора s. Процесс-сервер может продолжать слежение за состоянием объявленного гнезда, поддерживая связь с клиентом по отдельному каналу.
Вот мы и закончили рассматривать функции необходимые тебе для соединения процесса-клиента и процесса-сервера. Немного позже мы рассмотрим пример использования всего того, что мы тут наболтали, а пока давай ещё потрудимся над тем, как посылать и принимать пакеты и начнём мы с функции отправки пакетов, потому что для того, чтобы что-то принять, необходимо сначала отправить. И поможет нам в отправке пакетов функция send. int send ( SOCKET s, const char FAR * buf, int len, int flags );
s - как всегда это дескриптор гнезда buf - указатель на посылаемые данные len - размер данных Функция возвращает количество фактически переданных байт. Параметр flags может содержать значение MSG_DONTROUTE - определяет, что данные не должны быть подчиненны маршрутизации, MSG_OOB (послать данные out-of-band "через таможню"), если посылаемые данные не учитываются в общем информационном обмене между взаимодействующими процессами. Длинна сообщения не должна превышать значения в SO_MAX_MSG_SIZE. Приём данных осуществляется функцией recv:
http://www.vr-online.ru
13
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
int recv ( SOCKET s, char FAR* buf, int len, int flags ); buf - массив для приема данных len - ожидаемый объем данных Flags - могут быть установлены таким образом, что поступившее сообщение после чтения и анализа его содержимого не будет удалено из очереди, или настроены на получение данных out-of-band. MSG_PEEK - Данные будут скопированы в буфер, но не удалены из входной очереди. MSG_OOB - то же что и в функции send. Возвращаемое значение - количество байт, фактически переданных пользовательской программе Для дейтаграммных версиях используются функции sendto и recvfrom. Обе функции работают так же как и send и recv, только в качестве дополнительных параметров указываются адреса.
Теперь мы научились получать соединения, посылать данные, осталось только научится закрывать соединения. Функция shutdown закрывает гнездовую связь: int shutdown ( SOCKET s, int how ); mode - указывает, какой из сторон (посылающей, принимающей или обеим вместе) отныне запрещено участие в процессе передачи данных. Если SD_RECEIVE, то последующие сообщения от разъёма будут отвергнуты. SD_SEND - отвергает последующие отправки сообщений. SD_BOTH - отвергает и приём, и передачу сообщений. Функция сообщает используемому протоколу о завершении сеанса сетевого взаимодействия, оставляя, тем не менее, дескрипторы гнезд в неприкосновенности. Но эта функция не освобождает дескриптор гнезда.
Освобождение дескриптора гнезда происходит функцией closesocket: int closesocket (SOCKET s); s - дескриптор гнезда. Ну вот мы и закончили рассматривать основные функции для работы с гнёздами. Я не могу, даже заикнутся о том, что мы рассмотрели все функции, это были только
http://www.vr-online.ru
14
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
основные, все функции мы не сможем рассмотреть, но ещё с некоторыми из существующих мы познакомимся в процессе рассмотра примеров. Конечно, полученных знаний тебе хватит, чтобы самостоятельно написать простенькую программу, а дополнительную информацию получить их помощи, но лучше если ты немного наберёшься терпения и подождёш следующего выпуска журнала.
Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
15
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Программирование в Delphi глазами хакера Автор: Фленов Михаил aka Horrific Из книги ты узнаешь: • Кто такой Хакер и как им стать; • Как создавать программы маленького размера; • Как оптимизировать код программы; • Как заставить летать кнопку «Пуск»; • Научишься контролировать системную палитру; • Научишься изменять разрешение экрана из своих программ; • Увидишь множество шуточного кода; • Узнаешь, как подсматриваем пароли, спрятанные под звездочками; • Напишешь программу мониторинга запускных файлов и клавиатурный шпион; • Сможешь портить окна чужих программ; • Как создавать окна неправильной формы; • Научишься работать с сетью через компоненты Delphi и увидишь как создаются сканеры портов, утилиты ping и др. • Узнаешь, как работать с сетью на уровне библиотеки WinSock; • Узнаешь, как работать с железом И многое другое. Посмотри на содержимое диска, и ты поймёшь, что он стоит того, чтобы купить эту книгу с диском: \Headers - Все необходимые заголовочные файлы, которые нужно будет подключать к Delphi для компиляции некоторых примеров \Source - Исходные коды своих простых программ, чтобы вы могли ознакомиться с реальными приложениями. Их немного, но посмотреть стоит. \Soft - Инсталляционный пакет программы Adobe Acrobat Reader v5.0. Если у вас нет этой программы, то вы должны её установить, чтобы можно было читать документацию, расположенную на диске. \Vr-online - Полная копия сайта автора, а это 100 мегабайт документации, полезной информации, исходных кодов и компонентов. Здесь же вы можете найти мою книгу "Библия Delphi" - в электронном виде. В ней вы найдёте все необходимые для понимания этого материала основы и если вы ещё ни разу не видели Delphi, то после прочтения этой книги вы сможете понять всё описанное здесь. \Документация - Дополнительная документация, которая может понадобиться для понимания каких-то глав. \Иконки - В этой директории вы найдёте большую коллекцию иконок, которые вы можете использовать в своих программах. Эту коллекцию я подбирал достаточно долго и все иконки хорошего качества. \Компоненты - Дополнительные компоненты, которые будут использоваться в примерах книги. \Программы - Программы, которые пригодятся в программировании. Среди них Header Convert - программа, которая конвертирует заголовочные файлы с языка С на Delphi и ASPack - программа сжатия запускных файлов. Спрашивай книгу в книжных магазинах своего города!!!
http://www.vr-online.ru
16
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
WinSock: Первая сетевая программа Сегодня я продолжу рассказывать о функциях для работы с гнёздами. Возьми исходники проги, по которой мы будем обучаться в файле sockets.zip. Программа вытаскивает сведения о структуре сети. Функции из этой программы я ещё не рассматривал, поэтому новой инфы тебе будет достаточно. Меня очень сильно поругали за то, что примеры я даю на Delphi, а функции в этом разделе описываются в стандарте языка С++. Ну что поделаешь если я привык сетевые функции так описывать. Сегодня я решил исправиться, все функции будут описываться в стандарте Delphi, как я это делаю в других статьях журнала.
Рис 1. Форма Ты можешь полюбоваться результатом работы сегодняшней проги на рисунке 1. А я перехожу к рассмотрению кода. Я не буду объяснять работу каждой строчки, как в других статьях, а объясню только сетевые функции. Если ты читал другие мои статьи и имеешь представление о Delphi, то с работой программы сможешь разобраться сам. function WNetOpenEnum( dwScope, dwType, dwUsage: DWORD; lpNetResource: PNetResource; var lphEnum: Thandle ): DWORD; stdcall; Эта функция запускается у меня первой. Она открывает перечисление сетевых устройств в локальной сети. Рассмотрим передаваемые ей параметры: •
dwScope - Какие ресурсы будут включаться в перечисление. Возможны комбинации следующих значений: o RESOURCE_GLOBALNET - все ресурсы сети; o RESOURCE_CONNECTED - подключённые; o RESOURCE_REMEMBERED - запомненные;
http://www.vr-online.ru
17
VR-online Journal (Фленов Михаил & VR-Team) •
•
•
•
Для программистов №2
dwType - тип ресурсов включаемых в перечисления. Возможны комбинации следующих значений: o RESOURCETYPE_ANY - все ресурсы сети; o RESOURCETYPE_DISK - сетевые диски; o RESOURCETYPE_PRINT - сетевые принтеры; dwUsage - использование ресурсов включаемых в перечисления. Возможны значения: o 0 - все ресурсы сети; o RESOURCEUSAGE_CONNECTABLE - подключаемые; o RESOURCEUSAGE_CONTAINER- контейнерные; lpNetResource - Указатель на структуру NETRESOURCE . Если этот параметр равен нулю, то перечисление начнётся с самой верхней ступени иерархии сетевых ресурсов. Ноль я ставлю для того, чтобы получить самый первый ресурс. После этого я передаю в качестве этого параметра указатель на уже найденный ресурс. Тогда перечисление начнётся с найденного и продолжится дальше. Так я повторяю, пока не найдутся все ресурсы. lphEnum - Это указатель, который понадобится в функции WnetEnumResource
Теперь нужно рассмотреть структуру NETRESOURCE . В Delphi есть три разновидности этой функции: NETRESOURCE, NETRESOURCEА и NETRESOURCEW. Отличаются они последней буквой в названии и типом строк. _NETRESOURCEA = packed record dwScope: DWORD; dwType: DWORD; dwDisplayType: DWORD; dwUsage: DWORD; lpLocalName: PAnsiChar; lpRemoteName: PAnsiChar; lpComment: PAnsiChar; lpProvider: PAnsiChar; Что такое dwScope, dwType и dwUsage ты уже знаешь, поэтому их я опускаю. А вот остальные я рассмотрю. •
• • • •
dwDisplayType - Как должен отображаться ресурс: o RESOURCEDISPLAYTYPE_DOMAIN - это домен; o RESOURCEDISPLAYTYPE_GENERIC - нет значения; o RESOURCEDISPLAYTYPE_SERVER - сервер; o RESOURCEDISPLAYTYPE_SHARE - разделяемый ресурс; lpLocalName - локальное имя lpRemoteName - удалённое имя lpComment - комментарий lpProvider - Хозяин ресурса. Параметр может быть 0, если хозяин неизвестен.
Теперь можно переходить к следующей функции: function WNetEnumResource( hEnum: THandle; var lpcCount: DWORD; lpBuffer: Pointer; var lpBufferSize: DWORD ): DWORD; stdcall; • •
hEnum - указатель на возвращённое функцией WNetOpenEnum значение. lpcCount - максимальное количество возвращаемых значений. Не стесняйся, ставь 2000. Если ты засунешь сюда 0xFFFFFFFF, то перечисляться все ресурсы. После выполнения, функция засунет сюда фактическое число найденных ресурсов.
http://www.vr-online.ru
18
VR-online Journal (Фленов Михаил & VR-Team) • •
Для программистов №2
lpBuffer - указатель на буфер, в который будет помещён результат. lpBufferSize -Размер буфера.
После вызова этой функции, я добавляю найденные ресурсы с помощью: NewNode := ShowResource(ParentNode, ResourceBuffer[i]); ShowResource выглядит вот так: function ShowResource(const ParentNode: TTreeNode; Res: TNetResource): TTreeNode; var Str:String; index:Integer; begin Result:=ParentNode; if Res.lpRemoteName=nil then exit; Str:=string(Res.lpRemoteName); index:=Pos('\',Str); While index>0 do begin Str:=Copy(Str,index+1,Length(Str)); index:=Pos('\',Str); end; Result := TreeTreeRes.Items.AddChild(ParentNode, Str); end; Всё очень просто и со вкусом. На сегодня хватит. Поговорим о сетях в следующем номере. Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
19
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Ты ищешь хорошую книгу по Delphi? Зайди на www.vr-online.ru и скачай полный электронный вариант Библии Delphi от Фленова Михаила абсолютно бесплатно. Эта книгу научит тебя программировать, даже если ты никогда в жизни не написал ни строчки кода. В ней описано всё, начиная от основ программирования и заканчивая реальными примерами программ и задач, которые программисты решают каждый день. Библия Delphi – самая иллюстрированная и самая бесплатная книга. По ней научились программировать множество людей и ты тоже сможешь.
http://www.vr-online.ru
20
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
OpenGL: Третье измерение и тест глубины Продолжаем знакомиться с OpenGL. В прошлой статье я познакомил тебя с инициализацией и двумя примитивами: точки и линии. Сегодня ты ещё немного узнаешь о примитивах и познакомишся с тестом глубины. Загрузи проект из прошлой статьи и исправь процедуру FormPaint вот так (если у тебя здесь): нет исходников прошлого занятия, то возьми уже готовые procedure TForm1.FormPaint(Sender: TObject); var ps:TPaintStruct; begin BeginPaint(Handle,ps); glClearColor(1,0.5,0.5,1); glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); glRotated(45,1,0,0); glColor3f (0.0, 1.0, 0.0); glBegin(GL_QUAD_STRIP); glVertex3f(1.25,1.25,1); glVertex3f(1.25,-1.0,1); glVertex3f(-1,1.25,0); glVertex3f(-1,-1.0,0); glEnd; glColor3f (1.0, 1.0, 0.0); glBegin(GL_QUAD_STRIP); glVertex3f(1.0,1.0,0); glVertex3f(1.0,-1.25,0); glVertex3f(-1.25,1.0,1); glVertex3f(-1.25,-1.25,1); glEnd; glFlush(); swapBuffers(dc); EndPaint(Handle,ps); end; Если ты запустишь сейчас программу, то увидишь нечто похожее на рисунок 1. Что же нового произошло? Во-первых это очистка экрана: glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
http://www.vr-online.ru
21
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Рис 1. Форма Я включил сюда очистку буфера глубины GL_DEPTH_BUFFER_BIT. Хотя глубину я ещё не использовал, но всё же сегодня я это сделаю. Второе это glRotated(g,x,y,z). Эта функция поворачивает всю сцену на угол g, по осям x,y,z. В нашем случае сцена повернётся по оси X на 45 градусов. Если надо повернуть по оси y, то третий параметр нужно выставить в 1. glBegin(GL_QUAD_STRIP); glVertex3f(1.0,1.0,0); glVertex3f(1.0,-1.25,0); glVertex3f(-1.25,1.0,1); glVertex3f(-1.25,-1.25,1); glEnd; Эта конструкция рисует квадрат из четырёх точек. Так мы постепенно будем знакомиться со всеми возможными примитивами. В отличии от прошлой статьи, где я не использовал у вершин параметр Z, здесь я его использую. Поэтому наша сцена получилась трёхмерной. Теперь ещё раз взгляни на результат работы программы, он немного не правильный. Два наших квадрата должны пересекаться в середине, но этого не происходит. Это связано с тем. Что не включён тест глубины. Для того, чтобы его включить, ты должен перед рисованием квадратов написать glEnable(GL_DEPTH_TEST). Подкорректируй свою программу (или мой исходник) вот так. glEnable(GL_DEPTH_TEST); glColor3f (0.0, 1.0, 0.0); glBegin(GL_QUAD_STRIP); glVertex3f(1.25,1.25,1); glVertex3f(1.25,-1.0,1); glVertex3f(-1,1.25,0); glVertex3f(-1,-1.0,0); glEnd; glColor3f (1.0, 1.0, 0.0); glBegin(GL_QUAD_STRIP); glVertex3f(1.0,1.0,0); glVertex3f(1.0,-1.25,0); glVertex3f(-1.25,1.0,1); glVertex3f(-1.25,-1.25,1); glEnd;
http://www.vr-online.ru
22
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
glDisable(GL_DEPTH_TEST);
Рис 2. Форма Теперь у тебя получится правильная сцена, как на рисунке 2. Для того, чтобы регулировать функцией, отвечающей за тест глубины, ты можешь воспользоваться процедурой glDepthFunc. В качестве параметров ты можешь передавать следующие значения: • • • • • • • •
GL_NEVER - тест не выполнится никогда GL_LESS - тест выполнится, если текущее значение Z меньше чем сохранённое. GL_EQUAL - тест выполнится, если текущее значение Z равно сохранённому. GL_LEQUAL - тест выполнится, если текущее значение Z меньше или равно сохранённому. GL_GREATER - тест выполнится, если текущее значение Z больше сохранённого. GL_NOTEQUAL- тест выполнится, если текущее значение Z не равен сохранённому. GL_GEQUAL - тест выполнится, если текущее значение Z больше или равен сохранённому. GL_ALWAYS - тест выполнится всегда.
Эти функции ты можешь включать после включения теста глубины. Например: glEnable(GL_DEPTH_TEST); glDepthFunc(glLess); Попробуй поиграть с этими функциями, а я удаляюсь. Увидимся в следующем номере. Исходники находятся в файле opengl.zip, а заголовочные файлы ты можешь найти на нашем сайте в разделе Download. Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
23
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Форматы файлов: Алгоритм сжатия для GIF Как я и обещал, сегодня я расскажу об алгоритме сжатия в GIF. Этот алгоритм основан на LZW, но со своими небольшими особенностями, которые мы рассмотрим немного позже, а сейчас мы рассмотрим сам алгоритм LZW. Я сначала хотел показать алгоритм, а потом дать объяснение, но понял, что это будет сложно для тебя. Поэтому сейчас ты прослушаешь напутственное слово, в качестве которого будет объяснение инициализации. Для начала представим, что нам надо сжать данные, которые состоят из 256 различных символов. Это число не случайно, оно равно максимальному количеству цветов в GIF файлах. На этапе инициализации, ты должен закодировать все возможные значения символов: первому символу назначить "код 1", второму символу - "код 2" и так далее. При сжатии растровых данных GIF файла эта задача упрощается, потому что в качестве символов используются индексы цвета, которые изменяются от 0 до 255. Это значит, что тебе нужно просто создать массив с этими числами. Задача усложниться только если ты будешь сжимать символьные данные, а не числовые, или если числовые будут содержать пропуски (1, 3, 6 …). Теперь рассмотрим сам алгоритм: 1. 2. 3. 4.
Инициализация массива кодов index = пустая строка index + следующий символ Есть index в массиве?
да:
(BK:=Код для index перейти на 3-й шаг) иначе:(добавить index в массив кодов вывести ВК в выходной массив index=текущий символ перейти на 4-й шаг)
Первый этап мы уже рассмотрели и останавливаться на нём не имеет смысла. На втором этапе мы инициализируем переменную index, в которой будем хранить последовательность символов подлежащих сжатию. На третьем этапе мы присваиваем этой переменной следующий символ (при первом проходе это первый символ) который подлежит сжатию. Четвёртый этап, мы проверяем наличие последовательности символов index в массиве кодов. На первом этапе index равен одному символу, а в массиве кодов закодированы все возможные символы, поэтому результат обязательно должен быть "да". Если "да", то мы сохраняем в переменной "ВК" код этого последовательности символов и переходим на шаг 3.
http://www.vr-online.ru
24
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Если "нет", то добавляем index в массив кодов, переменную "ВК" добавляем в выходной массив и в index помещаем текущий символ. Переходим на шаг 4. Если что-то непонятно, то сейчас всё равно поймёшь. Сейчас я рассмотрю реальный пример. Для удобства, коды я буду представлять со значком #. Представим, что нужно сжать следующую последовательность данных: 4814810147. Это последовательность символов от 0 до 8. Инициализируем массив, где 0 равен "коду 0", 1 = "код 2" и так до 8. В index присваиваем первый символ (4). Проверяем его наличие в массиве кодов. Результат, конечно же "да" (код 4), поэтому ВК=#4. Переходим на шаг 3. Добавляем в index следующий символ, теперь index=48. Такова в массиве кодов нет, поэтому мы добавляем "48" под номером 9, а в выходной массив записываем "ВК". В Index засовываем 8 и переходим на шаг 4. Этот символ опять есть. Записываем код в ВК и переходим на шаг 3. Прибавляем к index "1". Такова символа опять нет, поэтому добавляем в таблицу символ "81" под кодом #10. На следующем этапе мы получаем число "14", которого опять нет, что делать ты уже догадываешься. Следующий этап 48, оно есть, поэтому мы записываем в ВК=#9 и записываем в index=481. Такова символа нет, поэтому добавляем его, а в index=1 и продолжаем. И так далее. Итоговый массив у тебя должен получится вот такой: #4 #8 #1 #9 #1 #0 #1 #11 #7. Если у тебя получился такой массив, то можешь продолжать чтение, если нет, то перечитай статью заново. Если снова не получится, то иди к почтальону, возможно, что ошибся я. Этот алгоритм съедает достаточно хорошие ресурсы. Особенно это сказывается при поиске символа в массиве кодов, потому что массив постоянно разрастается. Да и я не оптимизировал свой базар для того, чтобы ты мог понять, о чём тут талдонят. Если ты разобрался с сжатием, то давай перейдём к распаковке. Её я начну с реального примера, а именно распакуем нашу последовательность #4 #8 #1 #9 #1 #0 #11 #7. Инициализация абсолютно такая же. Берём первый символ. #4 он есть в таблице, поэтому на выходе будет 4. Берём следующий код #8, ему соответствует символ 8. На выход добавляем 8, а в массив добавляем предыдущий символ + первый символ этого, т.е. 48. Берём следующий код #1, что соответствует 1. На выход добавляем 1, а в массив 81. Берём следующий код #9, что соответствует 48. На выход добавляем 48, а в массив предыдущий символ + первый символ этого, т.е. 14. Берём следующий код #1, что соответствует 1. На выход добавляем 1, а в массив предыдущий символ (48)+ первый символ этого, т.е. 481. И так далее. Если ты всё сделал правильно, то массивы при сжатии и распаковке будут одинаковыми, и результат совпадёт. Надеюсь, ты понял алгоритм. Но во время распаковки встречаются и подводные булыжники. Например, ты сжал вот такую последовательность: 333333, при максимуме значений = 8. Результатом сжатия будет #3, #9, #10 и так далее. На этапе распаковке ты наткнёшься на то, что второго символа не будет в твоём массиве. Каким местом ты должен будешь догадываться, чему равен код #9. А тем, что это может быть только 33. На следующем этапе опять ты встречаешь код #10, которого нет в таблице. Тем же самым местом ты должен догадаться, что это будет 333. Теперь я учёл все подводные плиты и другие дьявольские сооружения, поэтому переходим к особенностям GIF.
http://www.vr-online.ru
25
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
В GIF файлах инициализация происходит не как у людей. Помимо начальной таблицы здесь присутствуют два специальных кода. Например, если картинка имеет цветовое разрешение 2 бит, то твоя таблица должна состоять из 4-х значений. Но это не так. Спецификация GIF добавляет ещё два, поэтому будет 6. Если у тебя монохромная картинка, то твои коды будут под номерами 0 и 1, а два специальных под номерами 4 и 5. В остальных случаях специальные коды идут вплотную к твоим. Два специальных кода это "CC" - код очистки, и EOI конец информации. Если вы сжимаете GIF, то "СС" должен быть первым кодом, и так каждый раз, когда код достигает FFF. Это связано с тем, что GIF поддерживает только 12 битное сжатие. При распаковке, как только встречается код "СС" нужно реинсталировать массив. Я надеюсь, что эта статья поможет тебе в понимании LZW сжатия, в особенности в применении к GIF файлам. В будущем я продолжу рассказы о графических форматах, а сегодня можешь отдохнуть.
Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
26
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Геймер: Введение в DirectDraw DirectDraw предназначен для непосредственной работы с видеопамятью. Для этого он по возможности использует аппаратно-зависимый доступ. Самое интересное для нас это то, что аппаратно-зависимый доступ выглядит как независимый. Это означает, что одна и та же функция будет использовать аппаратные средства разных видеокарт. Если видео карта не поддерживает каких-либо из возможностей DirectDraw, то они эмулируются. Функции DirectDraw находятся в библиотеке Ddraw.dll и исполнен в виде COM интерфейсов. Я не буду глубоко вдаваться в подробности аппаратной работы (HAL) и эмуляции (HEL) с помощью DirectDraw , они тебе на фиг не нужны. Хотя все авторы книг обожают забивать место этой ерундой. Они наверно считают, что если ты будешь это знать, то твоя прога будет работать лучше. Чушь. Поэтому давай переходить к делу. DirectDraw , грубо говоря, состоит из: • • •
DirectDrawSurface - область видеопамяти. К ней можно обращаться напрямую и производить практически любые действия. DirectDrawPalette - интерфейс для работы с палитрами. Он нужен, если ты настроился на 256 или не дай бог 16 цветную игру. DirectDrawClipper - этот интерфейс отвечает за обрезания. Он нужен, если ты пишешь оконное приложение. В этом случае тебе понадобится обрезать всё, что выходит за пределы окна.
Я думаю, что хватит балаболить. Пора перейти к конкретному примеру. В качестве примера у нас будет бегающий по экрану красный квадратик. Для создания DirectX приложений, тебе понадобятся заголовочные файлы. Их ты можешь взять здесь. Я не буду приводить исходники проги, возьми их здесь, а я буду только объяснять, что происходит на каждом этапе. Когда создаётся форма (процедура FormCreate), я инициализирую переменные FRect, FYAdd и FХAdd. Я их буду использовать во время рисования. После этого я устанавливаю процедуру, которая будет вызываться в свободное время Application.OnIdle := ProgIdle; . Если приложение ничего не делает, то управление будет передаваться процедуре ProgIdle . Когда форма готова к употреблению я в событии FormShow вызываю процедуру StartDX , которая инициализирует DirectX. Самая первая функция, на которую ты наткнёшься в процедуре StartDX будет DirectDrawCreate . function DirectDrawCreate( lpGUID: PGUID; out lplpDD: IDirectDraw;
http://www.vr-online.ru
27
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
pUnkOuter: IUnknown): HResult; stdcall; Эта функция создаёт объект DirectDraw. Эта функция может вызываться несколько раз, но для нормальной работы достаточно и одного. Рассмотрим подробнее её параметры: lpGUID - используемый для вывода драйвер. Возможные значения: DDCREATE_HARDWAREONLY - использовать только аппаратные возможности; DDCREATE_EMULATIONONLY - использовать только эмуляцию; nil - использовать аппаратные возможности и эмуляцию. IDirectDraw - указатель на объект IDirectDraw, который будет инициализирован. pUnkOuter - зарезервирован до лучших времён, обязательно должен быть nil. Возвращаемые значения: DDERR_DIRECTDRAWALREADYCREATED, DDERR_GENERIC, DDERR_INVALIDDIRECTDRAWGUID, DDERR_INVALIDPARAMS, DDERR_NODIRECTDRAWHW, DDERR_OUTOFMEMORY. Если всё прошло удачно, то возвращает DD_OK. После создания объекта DirectDraw необходимо получить доступ к DirectDraw2. Для этого мы вызываем метод QueryInterface у ADirectDraw. В качестве первого параметра передаётся GUID, который говорит, что нам нужен DirectDraw2. В качестве второго параметра передаётся указатель на DirectDraw2, который будет инициализирован. Зачем нам нужен DirectDraw2? Это более новая версия DirectDraw, которая появилась в DirectX2 (если мне не изменяет память). DirectDraw сохранён для совместимости со старыми приложениями, поэтому мы будем прогрессивными и воспользуемся DirectDraw2. После создания DirectDraw2, DirectDraw1 больше не нужен. Он использовался только для инициализации DirectDraw2, поэтому ссылку на него можно смело уничтожать ADirectDraw:=nil. Затем, с помощью функции SetCooperativeLevel объекта DirectDraw2 мы устанавливаем флаги используемого режима работы. function SetCooperativeLevel (hWnd: HWND; - указатель на окно. dwFlags: DWORD - флаги. ): HResult; stdcall; С первым параметром всё ясно, я передал указатель на главную форму. Второй параметр, это флаги, которые могут иметь следующие значения: DDSCL_ALLOWMODEX - Позволяет использование ModeX режимов дисплея. DDSCL_ALLOWREBOOT - Позволяет реагировать на CTRL_ALT_DEL во время полноэкранного исключительного режима. DDSCL_EXCLUSIVE - Запрашивает исключительный уровень. DDSCL_FULLSCREEN-Указывает, что исключительный владелец режима будет ответственен за всю основную поверхность. GDI может игнорироваться.
http://www.vr-online.ru
28
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
DDSCL_NORMAL-Указывает, что прикладная программа будет функционировать как обычная прикладная программа Windows. DDSCL_NOWINDOWCHANGES -Указывает, что DirectDraw не позволяется минимизировать или восстановить окно прикладной программы при активации.
Внимание! Не все сочетания флагов ты можешь использовать. Например, ты не можешь установить флаг DDSCL_NORMAL вместе с DDSCL_EXCLUSIVE, потому что оконное приложение не может быть эксклюзивным.
Затем, с помощью функции SetDisplayMode объекта DirectDraw2 я устанавливаю размеры экрана и глубину цвета. Если ты будешь создавать оконное приложение, то эту функцию вызывать нельзя. Подумай сам, как можно отдельному окну задавать размеры всего экрана? function SetDisplayMode( dwWidth, //Ширина экрана dwHeight, //Высота экрана dwBpp: DWORD; //Число бит на пиксел dwRefreshRate: DWORD; // Частота развёртки (0-по использовать умолчанию) dwFlags: DWORD) // Зарезервирован, должен быть 0. : HResult; stdcall; После всего этого я создаю поверхности с помощью CreateSurface, всё того же объекта DirectDraw2. function CreateSurface( const lpDDSurfaceDesc: TDDSurfaceDesc;//Параметры поверхности out lplpDDSurface: IDirectDrawSurface; //Указатель на создаваемую поверхность pUnkOuter: Iunknown //Зарезервирован, должен быть nil ): HResult; Структура TDDSurfaceDesc заслуживает отдельного разговора: TDDSurfaceDesc = record dwSize: DWORD; //Размер структуры dwFlags: DWORD; //Флаги dwHeight: DWORD; //Высота поверхности dwWidth: DWORD; //Ширина поверхности case Integer of 0: ( lPitch: Longint; //Расстояние до начала следующей линии dwBackBufferCount: DWORD;//Число дополнительных буферов case Integer of 0: ( dwMipMapCount: DWORD; //Число уровней mipmap. dwAlphaBitDepth: DWORD;//Глубина альфа-буфера dwReserved: DWORD; //Зарезервирован lpSurface: Pointer; //Указатель на память связанной поверхности ddckCKDestOverlay: TDDColorKey;//Цветовой ключ для оверлея ddckCKDestBlt: TDDColorKey; //Цветовой ключ для Blt. ddckCKSrcOverlay: TDDColorKey; //Цветовой ключ для источника оверл. ddckCKSrcBlt: TDDColorKey; // Цветовой ключ для источника Blt ddpfPixelFormat: TDDPixelFormat;// Формат пиксела ddsCaps: TDDSCaps; //Возможности поверхности ); 1: (
http://www.vr-online.ru
29
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
dwZBufferBitDepth: DWORD;// Глубина z-буфера ); 2: ( dwRefreshRate: DWORD; //Частота обновления ); ); 1: ( dwLinearSize: DWORD ); end; dwFlags может принимать значения: • • • • • • • • • • • • • • • •
DDSD_ALL - Все входные члены имеют силу. DDSD_ALPHABITDEPTH - dwAlphaBitDepth имеет силу. DDSD_BACKBUFFERCOUNT - dwBackBufferCount имеет силу. DDSD_CAPS - ddsCaps имеет силу. DDSD_CKDESTBLT - ddckCKDestBlt имеет силу. DDSD_CKDESTOVERLAY - ddckCKDestOverlay имеет силу. DDSD_CKSRCBLT - ddckCKSrcBlt имеет силу. DDSD_CKSRCOVERLAY - ddckCKSrcOverlay имеет силу. DDSD_HEIGHT - dwHeight имеет силу. DDSD_LPSURFACE - lpSurface имеет силу. DDSD_MIPMAPCOUNT - dwMipMapCount имеет силу. DDSD_PITCH - lPitch имеет силу. DDSD_PIXELFORMAT - ddpfPixelFormat имеет силу. DDSD_REFRESHRATE - dwRefreshRate имеет силу. DDSD_WIDTH - dwWidth имеет силу. DDSD_ZBUFFERBITDEPTH - dwZBufferBitDepth имеет силу.
Остальные параметры ты можешь понять по комментариям. Единственное, что мне придётся расписать, так это структуру DDPIXELFORMAT: TDDPixelFormat = record dwSize: DWORD; // Размер структуры dwFlags: DWORD; // Влаги dwFourCC: DWORD;// FourCC код case Integer of 0: ( dwRGBBitCount: DWORD;// RGB биты на пиксель dwRBitMask: DWORD; // Маска для красных битов. dwGBitMask: DWORD; // Маска для зелёных битов. dwBBitMask: DWORD; // Маска для голубых :) битов. dwRGBAlphaBitMask: // DWORD; Маска для alpha канала ); 1: ( _union1a: DWORD; _union1b: DWORD; _union1c: DWORD; _union1d: DWORD; dwRGBZBitMask: DWORD; ); 2: ( dwYUVBitCount: DWORD; // YUV биты на пиксель dwYBitMask: DWORD; // Маска для Y битов dwUBitMask: DWORD; // Маска для U битов dwVBitMask: DWORD; // Маска для V битов dwYUVAlphaBitMask: DWORD; //Маска для alpha канала ); 3: (
http://www.vr-online.ru
30
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
_union3a: DWORD; _union3b: DWORD; _union3c: DWORD; _union3d: DWORD; dwYUVZBitMask: DWORD; ); 4: ( dwZBufferBitDepth: DWORD; //Глубина Z-буфера dwStencilBitDepth: DWORD; //Глубина ещё какого-то буфера dwZBitMask: DWORD; // Маска для Z dwStencilBitMask: DWORD; // Маска для Setnil буфера ); 5: ( dwAlphaBitDepth: DWORD; //Глубина Alpha канала ); 6: ( dwLuminanceBitCount: DWORD; dwLuminanceBitMask: DWORD; _union6a: DWORD; _union6b: DWORD; dwLuminanceAlphaBitMask: DWORD; ); 7: ( dwBumpBitCount: DWORD; dwBumpDuBitMask: DWORD; dwBumpDvBitMask: DWORD; dwBumpLuminanceBitMask: DWORD; ); end; dwFlags может содержать следующие значения • • • • • • • • • • • • •
DDPF_ALPHA - Формат пикселя описывает только alpha поверхность. DDPF_ALPHAPIXELS - Поверхность имеет информацию об alpha в формате пикселя. DDPF_COMPRESSED - Поверхность примет данные пикселя в определенном формате и сожмет их в течение операции записи. DDPF_FOURCC - FourCC имеет силу. DDPF_PALETTEINDEXED1 - цвет индексированн 1 битом. DDPF_PALETTEINDEXED2 - цвет индексированн 2 битами. DDPF_PALETTEINDEXED4 - цвет индексированн 4 битами. DDPF_PALETTEINDEXED8 - цвет индексированн 8 битами. DDPF_PALETTEINDEXEDTO8 - 1-, 2-, или с 4 битами цвета, индексированны к палитре с 8 битами. DDPF_RGB - RGB данные в структуре формата пикселя имеют силу. DDPF_RGBTOYUV - Поверхность примет RGB данные и транслирует их в течение операции записи к YUV данным. Формат данных, которые будут записаны, будет содержаться в структуре формата пикселя. Флажок DDPF_RGB будет установлен. DDPF_YUV YUV данные в структуре формата пикселя имеют силу. DDPF_ZBUFFER - Формат пикселя описывает поверхность z-буфера.
Я надеюсь, что следующая структура будет последняя на сегодня: DDSCAPS Typedef struct _DDSCAPS { DWORD dwCaps; } DDSCAPS, FAR * LPDDSCAPS;
http://www.vr-online.ru
31
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
В не ничего особенного нет, через неё мы просто передаём какие-нибудь флаги. С ними ты познакомишься по мере надобности. Так как при создании поверхности я запросил двойную буферизацию, мне необходимо получить доступ к обоим буферам. Указатель на первый мне вернула функция CreateSurface, а на присоединённый к ней второй буфер я получаю ссылку через функцию GetAttachedSurface : function GetAttachedSurface( var lpDDSCaps: TDDSCaps; // Адрес структуры DDSCAPS out lplpDDAttachedSurface: IdirectDrawSurface //Указатель на вторичную поверхность ): HResult; stdcall; На сегодня хватит, ты и так наверно уже загружен инфой по самые "нехочу". Со всем остальным, что здесь происходит я познакомлю тебя в следующем номере. А ты пока что поиграй с простеньким примером, который я написал для тебя. Исходники примера находятся в файле dx.zip
Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
32
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Геймер: Программирование с помощью DirectDraw Сегодня я продолжу рассмотрение DirectDraw. Никаких новых программ мы сегодня не напишем, а просто продолжим рассматривать пример из предыдущей статьи. Я очень много не успел рассказать, поэтому решил восполнить это сегодня. Мы остановились на том, что запустили в работу DirectDraw. После этого нет никаких заданий, поэтому программа выдаст сообщение OnIdlе. Как я уже говорил, это сообщение генерируется каждый раз, когда программе нечего делать. Мы создали обработчик этого события, поэтому посмотрим, что в нём происходит: procedure TForm1.ProgIdle(Sender: TObject; var Done: Boolean); var hr: HResult; begin Done := False; Drawer; while (True) do begin hr := FPrimarySurface.Flip(nil, 0); if (hr = DD_OK) then break; if(hr = DDERR_SURFACELOST) then hr := FPrimarySurface.Restore(); if(hr <> DDERR_WASSTILLDRAWING) then break; end; end; Это сообщение вызывается только один раз. Для того, чтобы оно сгенерировалось снова, я устанавливаю параметр Done равным false. После этого я вызываю функцию Drawer, потому что рисовать в этой функции не этично. Эта функция будет рисовать на вторичной поверхности, и я расскажу о ней позже. После прорисовки я переключаю поверхности с помощью функции FPrimarySurface.Flip. Эта функция делает вторичную поверхность первичной, а какой станет первичная, догадайся сам. Эта функция самая быстрая из всех, что позволяют вывести изображение на экран. Функция работает только в полноэкранном режиме. function Flip( lpDDSurfaceTargetOverride: IDirectDrawSurface; dwFlags: DWORD): HResult; stdcall; lpDDSurfaceTargetOverride - адрес структуры, описывающей поверхность. Именно эта поверхность станет первичной. Если этот параметр = nil, то поверхности будут переключаться циклически. В качестве влага ты можешь устанавливать DDFLIP_WAIT. В этом случае, если во время переключения произойдёт ошибка, то DirectDraw будет продолжать пытаться
http://www.vr-online.ru
33
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
переключить поверхность. Если этот параметр 0, то функция в случае неудачи сгенерирует ошибку и прекратит попытки. Если переключение удалось, то я выхожу из процедуры. Если нет, то я проверяю возвращённую ошибку. Если произошла ошибка DDERR_SURFACELOST, то значит поверхность потеряна. Это происходит, когда ты сворачиваешь окно или каким либо другим способом отдаёшь управление другой программе. Это происходит из-за того, что DirectDraw использует монопольный режим. Как только другое приложение получило возможность вывода, оно разрушает поверхности DirectDraw. Если это произошло, то я использую функцию FPrimarySurface.Restore(), которая восстанавливает поверхности. Если ты используешь палитру, то её тоже надо восстановить. Если произошла ошибка DDERR_WASSTILLDRAWING, то это значит, что поверхность ещё рисуется. Нельзя рисовать следующую поверхность, пока не прорисовалась эта. Теперь давай перейдём к процедуре Drawer. procedure TForm1.Drawer; var DC: HDC; begin if (FBackSurface.GetDC(DC) = DD_OK) then begin DrawShape(DC); FBackSurface.ReleaseDC(DC); end; end; FBackSurface.GetDC - возвращает контекст рисования на вторичном буфере. DrawShape -вызываю функцию, которая рисует на вторичном буфере. FBackSurface.ReleaseDC(DC) -Уничтожаю контекст рисования. И на последок у нас осталась только одна функция - DrawShape, которая непосредственно рисует фигуры. В этой процедуре я рисую средствами Windows. Мы с тобой уже рисовали на контексте устройства, поэтому следующие функции я не буду подробно описывать. procedure TForm1.DrawShape(var DC: HDC); var Brush, OldBrush: HBrush; begin //Создаю временную кисть чёрного цвета. Brush := CreateSolidBrush(RGB(0, 0, 0)); // Выбираю её в контекст OldBrush := SelectObject(DC, Brush); //Рисую квадрат. Во всех последующих проходах, этот квадрат //будет зарисовывать старое изображение. Rectangle(DC, FRect.left, FRect.top,FRect.left+30, FRect.top+30); //Востанавливаю старую кисть SelectObject(DC, OldBrush);
http://www.vr-online.ru
34
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
//Уничтожаю временную кисть DeleteObject(Brush); //Изменяю положение квадрата FRect.left := FRect.Left + FXAdd; FRect.Top := FRect.Top + FYAdd; //Проверяю на выход за пределы экрана if (FRect.left+50 > 795) then FXAdd := -2; if (FRect.left < 3) then FXAdd := 2; if (FRect.Top+50 > 595) then FYAdd := -2; if (FRect.Top < 3) then FYAdd := 2; //Создаю новую кисть и рисую квадрат в новом месте. Brush := CreateSolidBrush(RGB(200,10,10)); OldBrush := SelectObject(DC, Brush); Rectangle(DC, FRect.left, FRect.top,FRect.left+30, FRect.top+30); SelectObject(DC, OldBrush); DeleteObject(Brush); end; Со всем, что здесь происходит ты можешь разобраться по комментариям. Остаётся разобраться только с деструктором. При закрытии окна вызывается следующая процедура: procedure TForm1.FormDestroy(Sender: TObject); begin if(FDirectDraw <> nil) then begin FDirectDraw.FlipToGDISurface; FDirectDraw.SetCooperativeLevel(Handle, DDSCL_NORMAL); if FBackSurface <> nil then FBackSurface := nil; if FPrimarySurface <> nil then FPrimarySurface := nil; FDirectDraw := nil; end; end; FDirectDraw.FlipToGDISurface Отключает DirectDraw поверхности, делая поверхность GDI (в этой поверхности рисует Windows) первичной. SetCooperativeLevel - выставляю нормальный режим. После этого удаляются все поверхности. Вот теперь мы полностью рассмотрели пример, который я дал в прошлой статье. На сегодня новостей больше нет. Исходники примера можете взять в файле dx.zip
Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
35
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
SQL: Введение Начиная с этого номера я буду публиковать статьи посвящённые созданию запросов на языке SQL. В этих статьях я буду рассказывать теорию, а практически ты будешь тренироваться в разделе "Delphi и базы данных". SQL переводят на русский как Структурированный Язык Запросов . С помощью SQLзапросов можно создавать и работать с реляционными базами данных. Этот язык стал стандартом, поэтому если ты хочешь работать с базами данных, то ты должен знать этот язык как каждую дырку в своих зубах. SQL определяется Американским Национальным Институтом Стандартов и Международной Организацией по стандартизации (ISO) . Несмотря на это, некоторые производители баз данных вносят изменения и дополнения в этот язык. Эти изменения незначительны и основа остаётся совместимой со стандартом. Мы с тобой будем учить язык в чистом виде без изменений. Что такое реляционная база данных? Это таблица, в которой в качестве столбцов выступают поля данных, а каждая строка хранит данные. В каждой таблице должно быть одно уникальное поле, которое однозначно будет идентифицировать строку. Это поле называется ключевым. С ним мы уже познакомились в разделе "Delphi и базы данных", я его называл Key1 . Эти поля очень часто используются для связывания таблиц (как это мы уже делали). Но даже если у тебя таблица не связана, ключевое поле всё равно обязательно. Представь, что ты пишешь телефонную базу данных. Сколько у тебя будет "Ивановых"? Как ты будешь отличать их? Вот тут тебе поможет ключ. В качестве ключа желательно использовать численный тип и если позволяет база данных, то будет лучше, если он будет типа "autoincrement" (автоматически увеличивающееся/уменьшающееся число). Столбцы в базе данных, также должны быть уникальными, но в этом случае не обязательно числовыми. Их можно называть как угодно, лишь бы было уникально и тебе понятно, а остальное никого не касается. SQL может быть двух типов: интерактивный и вложенный . Первый - это отдельный язык, он сам выполняет запросы и сразу показывает результат работы. Второй - это когда SQL язык вложен в другой, как например в С++ или Delphi. В этом разделе я буду представлять, что у нас интерактивный SQL, а в практической части мы будем работать с вложенным (SQL будет вложен в Delphi). Интерактивный SQL более близок к стандартному, а во вложенном очень часто встречаются отклонения и дополнения. Например, в стандартном SQL различаются только два типа данных: строки и числа, но некоторые производители добавляют свои типы (Date, Time, Binary и т.д.). Числа в SQL делятся на два типа: целые (INTEGER или INT) и дробные (DECIMAL или DEC). Строки ограничены размером в 254 символа. Для дальнейшего понимания материала, нам понадобятся наглядные таблицы. Представим, что на твою прогу набралось много покупателей. В этом случае тебе понадобится база данных, чтоб эффективно вести их учёт. Прежде чем создавать поля,
http://www.vr-online.ru
36
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
нужно обязательно продумать, какие данные нужны, и как они будут представлены. В нашем случае понадобиться: • • • • • • • • •
Наименование проги (ProgName), которую купили (у тебя может быть несколько прог). Цена (Cost). Фамилия покупателя (LastName). Имя покупателя (FirstName). Адрес электронной почты (Email). Страна проживания (Country). Неплохо знать, где живёт твой пользователь. Количество купленных лицензий (LecNumber). Регистрационный код (RegNum).
На первый взгляд всё достаточно легко. Но это только на первый. Представим, что у тебя 2 проги, тогда у каждого второго в таблице будет одинаковое первое поле. Главная проблема, что это поле громоздкое, как минимум 20 символов. Чтобы избавится от этого недостатка необходимо вынести названия и цену прог в отдельную таблицу. То же самое со странами. Большинство твоих пользователей сосредоточено в трёх или четырёх странах, поэтому будет очень много повторений. Страны я тоже вынесу отдельно, и буду использовать как справочник. В этом случае, в основной таблице будут храниться только ссылки на справочник, что значительно уменьшит базу данных. Связь между этими таблицами будет сложная. Ну а кому сейчас легко? Посмотри на рис 1. Там показана эта связь. В качестве "Key1" выступают первичные ключи, а "Key2" вторичные. Что такое вторичный ключ ты поймёшь в процессе чтения моих статей.
Рис 1. Связь между таблицами. Теперь мы переходим к изучению команд SQL языка. Для этого мы создадим простой запрос. SELECT ProgName, Cost FROM Prog; SELECT все запросы начинаются с этой команды, и означает - "выбрать". После команды перечисляются имена полей, которые необходимо выбрать из базы данных. FROM - эта команда также присутствует во всех запросов и указывает на таблицу (или несколько таблиц) к которой направлен этот запрос.
http://www.vr-online.ru
37
VR-online Journal (Фленов Михаил & VR-Team)
Для программистов №2
Точка с запятой ";" используется в интерактивном SQL в качестве индекатора конца запроса. В некоторых системах эту роль выполняет "\". Во вложенном такого индикатора нет. В итоге мы получаем, что наш запрос выберет все строки с полями "ProgName" и "Cost" из таблицы "Prog". Как видишь, ничего сложного тут нет. Такой маленький запрос возвращает нам все поля из таблицы, кроме ключевого. Если нужно вывести всё, то можно перечислить все поля или воспользоваться значком "*". Следующий запрос выведет все поля из таблицы "prog": SELECT * FROM Prog; Если вы используете звёздочку, то выведутся все поля в том порядке, в каком они находятся в таблице. Если нужно вывести в другом порядке, то перечисли все поля в том порядке, в каком твое душе угодно. В SQL существует два ключевых слова, которые характеризуют параметры вывода информации: ALL и DISTINCT . Параметр All означает, что выводить нудно все строки, а DISTINCT означает, что ненужно выводить повторяющиеся строки. Следующий запрос выведет все возможные имена программ. Если в таблице встретится две строки с одинаковым именем программы, то он выведет это имя только один раз. SELECT DISTINCT ProgName FROM Prog; Если не задать параметр DISTINCT , то считается, что надо использовать параметр ALL . Я думаю, что на сегодня хватит. Сегодня ты познакомился с основами SQL и научился писать простые запросы к таблицам. В следующем номере я продолжу рассказ о SQL. Удачи тебе.
Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
38