VR-online JOURNAL Фленов Михаил and VR-Team VR-online для программистов №8 Delphi: Теория ................................................................................................................................3 Delphi: Потоки ...............................................................................................................................6 Delphi: Компоненты в runtime ....................................................................................................11 Delphi: Всё о DLL файлах...........................................................................................................15 SQL: Создание таблиц и индексов в runtime ............................................................................19 Delphi (ActiveX): Теория компонентов .....................................................................................21 WinAPI: Функции работы с файлами (Часть 1)........................................................................26 Java: Введение в визуализацию..................................................................................................32 SQL: Работа с полями .................................................................................................................35 Форматы файлов: WAV – звуковое сопровождение................................................................38 Программирование звука: Пример воспроизведения ..............................................................40
Copyright: VR-online Journal http://www.vr-online.ru
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Приветствую тебя дорогой читатель! Приветствую тебя от лица всей VR-Team! Недавно мне перевалило за 16 :) Праздновали днюху на каникулах т.к. на каникулах у всех есть свободное время. Тем не менее днюха чуть ли не сорвалась :( Большинство моих знакомых, после появления в их жизни компа, перестали просто гулять на свежем воздухе! Вот и у нас случилось тоже самое. Нескольких моих гостей не хватило на вечернюю прогулку, а тех, что хватило сразу же потянуло к ближайшему компу. В нашем случае этим компом оказался компьютерный гейм-клуб... Поиграть, конечно, весело, но куда лучше сходить в лес или просто устроить снежковые манёвры :] В наше время у большинства комп-совместимых людей всё общение сводится к цветочку 16 на 16 в трее. Я не говорю, что нужно раздолбать железяку и убежать в лес, нет... Но нужно использовать всё, что тебе даётся, по максимуму. Выйди на улицу, посмотри на небо. Видишь? Какая графика... ни одному компу такое не под силу. А природе под силу! Бесчисленное множество молекул существуют, дабы мог существовать ты и всё, что окружает тебя. На днях у нас в городе (Московская область) можно было видеть северное сияние (!) Замечательное зрелище... Ни каким графическим форматом не передать. Но опять же большинство людей предпочло сидеть до часу ночи за компом, нежели выйти на улицу и посмотреть на небо... На следующий день эти индивидуумы в ответ на мои рассказы задавали вопрос: "А где в Интернете можно фотки качнуть?" Это не деградация, случайно? Мне кажется, что ДА…
VR-online JOURNAL Horrific aka Фленов Михаил
INFO: ИДЕЯ И РЕАЛИЗАЦИЯ: Флёнов Михаил (Horrific) ГРАФИКА: Фленов Михаил, tr4sh
VR-Team: Crazy_Script, Del, Demogorgon, Fighter, Mish!, Negus, Spider NET, tr4sh INTERNET: WWW: http://www.vr-online.ru E-MAIL: horrific@vr-online.ru ДИЗАЙН САЙТА: tr4sh КОДИНГ САЙТА: Mish!
Данный журнал распространяется в виде PDF файлов. Вы можете выкладывать номера на любые носители без изменения внешнего вида журнала, без перевода в другие форматы, без изменения самого файла. В журнал запрещается вносить изменения. Перепечатка материалов запрещена. Журнал распространяется бесплатно, и ты можешь скачать его с нашего сайта, поэтому мы не смысла в перепечатывании видим материалов. Если ты хочешь стать автором журнала, то присылай свою статью на наш e-mail и мы обязательно включим её в очередной номер.
Читатель, не забывай, что природа есть главное чудо света. И мы должны её уважать. Ладно, что-то я разошёлся… :) Теперь усаживайся по удобнее и готовься впитывать то, что мы для тебя приготовили. А как начитаешься - вперёд на свежий воздух! Природа зовёт ;) Del
http://www.vr-online.ru
2
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Delphi: Теория Уже становится традицией, каждый месяц писать теорию о Delphi для начинающих. Сегодня не исключение. Я снова пишу основы, которые помогут разобраться с моими практическими примерами. Магическая точка с запятой (;). Я о ней уже писал, но всё же я решил повторится. Она ставиться в конце каждого оператора, как разделитель. Например, ты хочешь присвоить переменной F значение 5, а переменной W значение 4. Ты должен записать это так F:=5; W:=4;. Как видишь, точка с запятой ставиться в конце каждого выражения и служит их разделителем. Единственное, когда не надо ставить ;, так это перед словом else. Есть такая конструкция: if условие then Операция 1 //Здесь точка с запятой не нужна, потому что следом идёт else else оператор 2; Происходящее здесь звучит очень просто: Если условие выполнено то выполнить Операция 1 иначе выполнить оператор 2; Например: if i>0 then i:=5 else i:=10 Оператор for . for i:=0 to 100 do Оператор; // По русски это выглядит: от i равной 0 до 100 выполнять Оператор; Переменные. В Delphi есть несколько основных переменных: Integer - целое число. Shortint - короткое целое. Longint - длинное целое от –2147483648 до 2147483647.
http://www.vr-online.ru
3
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Real - вещественное (дробное), число с запятой. ShortString - строка до 255 символов. AnsiString - строка до 2 гига в ANSI кодировке. WideString - строка до 2 гига в Unicode. Boolean - булево. Может принимать значения true или false. Объявление переменных: Имя:Тип. Например, Perem:Integer. Это я объявил переменную Perem типа Integer. Теперь я могу присваивать ей целые значения. Например: Perem:=10, присваиваем переменной значение 10. Теперь Perem равно 10. Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
4
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Тебя мучает одна проблема, которую ты не можешь решить? Заходи на форум на нашем сайте, подумаем вместе!!! На нашем форуме ты можешь задать любой вопрос и получить ответ по следующим темам: • Программирование (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
5
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Delphi: Потоки Сегодня нам предстоит познакомится с потоками. Эта штука очень интересная и беспощадно нужная. Всё программирование звука будет написано с использованием потоков, так что вникай где суть. Представь, что ты хочешь вставить в свою прогу проверку орфографии. Если ты после каждой нажатой клавиши будешь проверять правильность слов, то твоя прога будет сумасшедше тормозить. А как же это делает MS Word? Очень просто, после запуска проги запускается поток, который в фоновом режиме производит проверку орфографии. Ты спокойно набираешь текст и даже не ощущаешь (почти) как параллельный процесс в свободное время производит сложнейшие проверки твоих каракуль. Любая программа содержит хотя бы один поток (главный), в котором она выполняется. Помимо этого, она может порождать любое количество дополнительных потоков, которые будут выполняться в фоновом режиме. У дополнительных потоков приоритет выставляется такой же как и у главного потока программы, но ты его можешь увеличить или уменьшить. Чем выше приоритет потока, тем больше на него отводится Рис 1. Создание нового объекта для потока процессорного времени. Да что тут распинаться, давай программировать. Со всем разберёмся в процессе. Создай новый проект. Поставь на форму ТRichEdit из палитры Win32 и один TLabel. Это мы создали форму, а теперь создадим поток. Выбери File->New (рисунок 1). Находишь там Thread Object , выделяешь и щёлкаешь "ОК". Появляется окошко, как на рисунке 2. Вводим имя потока (я ввёл TCountObj). Сохраняем весь проект. Главную форму под именем main (как всегда), а поток под именем MyThread. Рис 2. Ввод имени потока
http://www.vr-online.ru
6
VR-online Journal (Horrific and VR-Team)
Для программистов №8
После этого Delphi создаст вот такой код: unit MyThread; interface uses Classes; type TCountObj = class(TThread) private { Private declarations } protected procedure Execute; override; end; implementation { Important: Methods and properties of objects in VCL can only be used in a method called using Synchronize, for example, Synchronize(UpdateCaption); and UpdateCaption could look like, procedure TCountObj.UpdateCaption; begin Form1.Caption := 'Updated in a thread'; end; } { TCountObj } procedure TCountObj.Execute; begin { Place thread code here } end; end. Это новый поток. У объекта есть только одна функция Execute . В этой функции мы и будем писать код потока. Напишем вот что: procedure TCountObj.Execute; begin index:=1; //Запускаем бесконечный счётчик while index>0 do begin Synchronize(UpdateLabel); Inc(index); if index>100000 then index:=0; //Если поток остановлен, то выйти. if terminated then exit; end; end; index я объявил как integer в разделе private потока. Там же я объявил процедуру UpdateLabel. Эта процедура выглядит так:
http://www.vr-online.ru
7
VR-online Journal (Horrific and VR-Team)
Для программистов №8
procedure TCountObj.UpdateLabel; begin Form1.Label1.Caption:=IntToStr(Index); end; И последнее, что я сделал - подключил главную форму в раздел uses, потому что я обращаюсь к ней в коде выше (Form1.Label1.Caption). Теперь о магической функции Synchronize. В качестве параметра ей передаётся процедура UpdateLabel, которая производит вывод в главную форму. Для чего нужно вставлять вывод на экран в Synchronize? Библиотека VCL имеет один косяк - она не защищена от потоков. Если главная форма и поток попробуют одновременно вывести что-нибудь в одну и ту же область экрана или компонент, то программа рухнет как эфелева башня. Поэтому весь вывод на форму нужно выделять в отдельную процедуру и вызывать эту процедуру с помощью Synchronize. Что происходит во время вызова Synchronize? В этот момент поток останавливается и управление передаётся главному потоку, который и произведёт обновление. Наш поток готов. Возвращаемся к главной форме. Я её сделал, как на рис 3. В раздел uses (самый первый, который идёт после interface) я добавил свой поток MyThread. В разделе private я объявил переменную co типа TCountObj (объект моего потока). По нажатию кнопки написал такой код:
"Запустить"
я
procedure TForm1.Button1Click(Sender: TObject); begin co:=TCountObj.Create(true); co.Resume; co.Priority:=tpLower; end;
Рис 3. Главная форма
В первой строке я создаю поток co. В качестве параметра может быть true или false. Если false, то поток сразу начинает выполнение, иначе поток создаётся, но не запускается. Для запуска нужно использовать Resume, что я делаю во второй строке. В третьей строке я устанавливаю приоритет потока поменьше, чтобы он не мешал работе основному потоку и выполнялся в фоне. Если установить приоритет повыше, то основной поток начнёт притормаживать, потому что у них будут одинаковые приоритеты. По кнопке "Остановить" я написал: procedure TForm1.Button1Click(Sender: TObject); begin co.Terminate; co.Free; end;
http://www.vr-online.ru
8
VR-online Journal (Horrific and VR-Team)
Для программистов №8
В первой строке я останавливаю выполнение потока, а во второй уничтожаю его. Попробуй запустить прогу, запустить поток (нажатием кнопки "Запустить") и понабирать текст в RichEdit. Текст будет набиратся без проблем, и в это время в ТLabel будет работать счётчик. Если бы ты запустил счётчик без отдельного потока, то ты бы не смог набирать текст в RichEdit, потому что все ресурсы программы (основного потока) уходили бы на работу счётчика. Итак, наш первый поток готов. При программировании звука мы напишем более полезные примеры с потоками. А сейчас ещё несколько полезных фишек: • •
• •
Suspend - приостанавливает поток. Для вызова просто напиши co.Suspend. Чтобы возобновить работу с этой же точки нужно вызвать Resume. Priority- устанавливает приоритет потока. Например Priority:=tpIdle; o tpIdle - поток будет работать только когда процессор бездельничает. o tpLowest - самый слабый приоритет o tpLower - слабый приоритет o tpNormal - нормальный o tpHigher - высокий o tpHighest - самый высокий o tpTimeCritical - критичный (не советую использовать, потому что может грохнуть систему). Suspended - если этот параметр true, то поток находится в паузе. Terminated - если true, то поток должен быть остановлен.
Вот и всё. С потоками окончено. Исходники примера находятся в файле delphi1.zip Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
9
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Реклама в журнале 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
10
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Delphi: Компоненты в runtime Сегодня я написал маленький примерчик, который ответит сразу на два поставленных мне вопроса: как создавать компоненты в рантайме и как ими управлять. На оба я отвечал автору по e-mail и в обоих случаях адрес отправителя оказывался ошибочным и ответ не находил своего получателя. Возможно таким образом мои читатели увидят ответ на свой вопрос. А в будущем, указывайте правильный e-mail. Что такое рантайм? Твоя прога может находиться в двух состояниях - дезайнтайм (время создания проекта) и реалтайм (время выполнения проекта). Мы сегодня будем создавать компоненты не рисованием на форме, а чистым кодом уже во время выполнения проги. Создай новый проект и брось на него два компонента TLabel. Всё остальное будем делать ручками. Рис 1. Форма Для начала, в разделе private объявим переменную CompList типа TList. TList - это "объект-контейнер", который может хранить в себе кучу других. Точнее сказать, он хранит только ссылки, но это не главное. Главное - TList позволяет хорошо управлять хранящимися в нём объектами. На событие OnCreate напиши: procedure TForm1.FormCreate(Sender: TObject); begin CompList:=TList.Create; end; Здесь мы инициализируем нашу переменную CompList с помощью объекта TList. Во время инициализации выделяется память под нашу переменную. Сразу же на событие OnDestroy пишем: procedure TForm1.FormDestroy(Sender: TObject); begin CompList.Free; end; Здесь мы освобождаем выделенную память для переменной CompList. Теперь в обработчике нажатия мышкой напишем: procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var TempPanel:TPanel;//Объявляю переменную для панели
http://www.vr-online.ru
11
VR-online Journal (Horrific and VR-Team)
Для программистов №8
begin //Создаю панель. В скобках у Create указан будущий владелец TempPanel:=TPanel.Create(Form1); TempPanel.Left:=X; //Устанавливаю левую и правую координату TempPanel.Top:=Y; //в X и Y позицию, где нажата кнопка мыши TempPanel.Width:=20; //Устанавливаю ширину TempPanel.Height:=20; //Устанавливаю высоту //Далее устанавливаю обработчик нажатия на эту панель TempPanel.OnMouseDown:=PanelMouseDown; //Добавляю панель в контейнер CompList (CompList.Add) //и сохраняю результат в TempPanel.Tag TempPanel.Tag:=CompList.Add(TempPanel); Form1.InsertControl(TempPanel); //Вставляю панель на форму end; Что это за свойство Tag у компонента TPanel? Это просто целое значение, которое ты можешь использовать по своему усмотрению. Я засовываю в TempPanel.Tag индекс панели в контейнере CompList, который мне возвращается при добавлении панели в контейнер. Это абсолютно не влияет на сам компонент, а мне этот индекс пригодится. Теперь об обработчике TempPanel.OnMouseDown. Я туда засунул имя функции PanelMouseDown. Но такой функции нет среди стандартных функция и среди моего проекта. Поэтому мы должны её создать сами. Как это сделать эффективно? Вот тебе мой совет: • • •
Мы создаём обработчик для TPanel, поэтому временно поставь один экземпляр на форму в произвольное место. Создай для него обработчик на OnMouseDown и переименуй его в PanelMouseDown. Напиши нужный текст (я его покажу ниже) и можно удалять временно созданный на форме экземпляр TPanel.
Таким образом, ты можешь быть уверен, что ошибок не будет, потому что Delphi сама пропишет функцию PanelMouseDown где надо. Если захочешь объявлять эту функцию вручную, то напиши в разделе private: procedure PanelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); Объявлять можно и до private, там где объявляет Delphi обработчики событий. А ниже опиши саму функцию procedure TForm1.PanelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin end; Теперь давай посмотрим на функцию PanelMouseDown: procedure TForm1.PanelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
http://www.vr-online.ru
12
VR-online Journal (Horrific and VR-Team)
Для программистов №8
begin Label1.Caption:=IntToStr(TPanel(CompList.Items[TPanel(Sender).Tag]).Left); Label2.Caption:=IntToStr(TPanel(Sender).Left); end; Здесь две строки. Обе строки выполняют одно и тоже, но по разному. Обе строки записывают в свой ТLabel левую позицию панели, по которой ты щёлкнул. Первая строка, чтобы получить левую позицию панели использует CompList, а вторая работает с панелью напрямую. Рассмотрим сначала вторую строку. В ней основным является выражение "TPanel(Sender).Left". Sender - передаётся нам функцией PanelMouseDown. В нём записан указатель на объект, который сгенерировал событие OnMouseDown. В нашем случае это будет указатель на панель, по которой ты щёлкнул. Так как мы точно уверены, что это панель, то мы так и показываем TPanel(Sender). Этим мы приводим Sender к TPanel и теперь можешь использовать все свойства и методы панели, для примера нам достаточно свойства Left. Если бы мы знали точное имя панели, то этого писать не пришлось бы. Но это невозможно, потому что все панели у нас используют один обработчик нажатия мышкой. Получив значение левой позиции, мы переводим целое значение левой позиции в строку с помощью IntToStr. Первоя строка очень похожа на вторую, только внутри TPanel() мы используем не Sender, а "CompList.Items[TPanel(Sender).Tag]", т.е. значение из конейнера. Чтобы получить первое значение из контейнера, нужно написать CompList.Items[0], для второго CompList.Items[1], для третьего CompList.Items[2] и т.д. Но по какой именно панели произведён щелчёк? Чтобы это узнать я пишу TPanel(Sender).Tag, то есть получаю свойство Tag (там хранятся индекс панели) панели сгенерировавшей событие. Далее, всё происходит так же. Запусти пример и пощёлкай по форме. По каждому щелчку будут создаватся панели. Потом попробуй пощёлкать по самим панелям. На двух TLabel будут появляться значения левой позиции панель, по которым ты щёлкал. Исходники примера находятся в файле delphi3.zip Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
13
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Программирование в 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
14
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Delphi: Всё о DLL файлах Мне уже пришло два письма с просьбой описать работу DLL файлов. Хотя этот номер уже перегружен статьями о Delphi, я решил не дожидаться следующего месяца и выполнить эту просьбу. Я думаю, что эта статья заинтересует многих. Готовься, сегодня мы напишем DLL файл и проект, который загрузит эту DLL. Начнём с создания DLL. Первое, что надо сделать, это создать соответствующий проект. Щёлки File->New. Перед тобой откроется уже знакомое окно создания проекта. Выбери пункт DLL и щёлкай "ОК" (рис. 1). Delphi создаст тебе пустой проект DLL файла. Выглядит он так: library ProjectDLL; { Здесь идёт куча информативных строк. }
Рис 1. Создание DLL
uses SysUtils, Classes; {$R *.RES} begin end. В качестве информативных строк тебя предупреждают, что если ты будешь использовать динамические строки, то надо подключить в раздел uses модуль ShareMem. Я этот модуль не подключил, потому что для примера мы не будем использовать строки. Удаляй эти комментарии, чтобы они не мозолили твои глаза. Мы засунем в DLL файл одну лишь процедуру и одну форму, поэтому сейчас мы должны это описать: library ProjectDLL; uses SysUtils, Classes; {$R *.RES}
http://www.vr-online.ru
15
VR-online Journal (Horrific and VR-Team)
Для программистов №8
exports ShowAbout index 10; begin end. Я добавил только одну строку exports ShowAbout index 10; . Что это означает? Ключевое слово export говорит о том, что я буду экспортировать процедуру или функцию. После этого идёт имя этой процедуры ShowAbout . Далее идёт ключевое слово index и число. Каждой процедуре, которую ты хочешь экспортировать ты должен назначить индекс или имя (можно и то и другое сразу). По этому индексу или имени программа будет вызывать экспортируемую функцию. Индексы и имена должны быть уникальными!!! Вот несколько примеров: exports Func1 Func2 Func3 Func4 Func5
index 10 name 'Fun', Insert, index 11, index 11,//Ошибка, такой индекс уже существует name 'Don';
Через индексы функции вызываются быстрее, поэтому я использую их. Объявлять можно и так: exports Func1 index 10 name 'Fun', exports Func2 Insert, exports Func3 index 11, Но это всё примеры. У нас будет только ShowAbout с индексом 10. Теперь щёлкаем File->New Form , чтобы создать новую форму. Нарисуй на ней чтонибудь, можно даже то, что сделал я (рис 2). Переходи в текст модуля. В разделе var, после объявления формы опиши процедуру Рис 2. Форма из DLL ShowAbout: var Form1: TForm1; procedure ShowAbout(Handle: THandle);export;stdcall; Опять присутствует ключ export и добавлен ещё stdcall. Второй ключ говорит о том, что нужно использовать стандартный вызов. Теперь напишем саму функцию после implementation и ключа {$R *.DFM}: procedure ShowAbout(Handle: THandle); begin //Установить указатель на приложение Application.Handle := Handle; //Создать форму Form1:= TForm1.Create(Application); //Отобразить Form1.ShowModal; //Очистить
http://www.vr-online.ru
16
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Form1.Free; end; Откомпилируй (Ctrl+F9) и DLL-файл готов. Можно закрывать этот проект (File->Close All) и создавать новый (File->New Application). В новом проекте переходим в текст формы и объявляем функцию ShowAbout: unit Unit2; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; procedure ShowAbout(Handle: THandle)stdcall; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; procedure ShowAbout; external 'ProjectDLL.dll' index 10; implementation Объявление происходит два раза. Первый раз после раздела uses и перед type нужно написать procedure ShowAbout(Handle: THandle)stdcall; . А второй раз в разделе var пишем procedure ShowAbout;external 'ProjectDLL.dll' index 10; . Здесь в кавычках стоит имя DLL файла. Пиши его полностью (вместе с расширением), потому что без расширения DLL будет не найдена в Windows NT. Обязательно соблюдай индексы и параметры процедуры. При втором объявлении параметры указывать не надо, только имя. Теперь ставим на форму кнопочку и пишем по её событию procedure TForm1.Button1Click(Sender: TObject); begin ShowAbout(Handle); end; Запускаем и наслаждаемся. Удачи тебе. Исходники примера забирай в файле delphi4.zip Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
17
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Ты ищешь хорошую книгу по Delphi? Зайди на www.vr-online.ru и скачай полный электронный вариант Библии Delphi от Фленова Михаила абсолютно бесплатно. Эта книгу научит тебя программировать, даже если ты никогда в жизни не написал ни строчки кода. В ней описано всё, начиная от основ программирования и заканчивая реальными примерами программ и задач, которые программисты решают каждый день. Библия Delphi – самая иллюстрированная и самая бесплатная книга. По ней научились программировать множество людей и ты тоже сможешь.
http://www.vr-online.ru
18
VR-online Journal (Horrific and VR-Team)
Для программистов №8
SQL: Создание таблиц и индексов в runtime Давно я уже не писал про базы данных. Сегодня я решил вернутся к этой проблеме, потому что недавно ко мне пришёл интересный вопросец: "Как создавать индексы". Поэтому я решил сегодня рассказать тебе, как создаются таблицы и индексы в рантайме. Для примера я бросил на форму две кнопки, один TEdit, TTable, TDataSource и DBGrid (см. рисунок 1). У DataSource1 свойство DataSet я поставил в Table1 , а у DBGrid в DataSource засунул DataSource1 . Всё это я сделал, чтобы можно было сразу увидеть созданную таблицу. Кнопки у меня такие: первая для создания таблицы, а вторая для индексирования. По нажатию первой, я намулевал вот что:
Рис 1. Форма
procedure TForm1.Button1Click(Sender: TObject); begin with Table1 do begin Active := False;//Отключаю базу TableName := Edit1.Text;//Задаю имя новой базы FieldDefs.Clear;//Очищаю все текущие поля with FieldDefs.AddFieldDef do//Добавляю новое поле begin Name := 'Field1';//Задаю имя нового поля DataType := ftAutoInc;//Задаю тип autoincrement Required := True;// Делаю поле обязательным end; with FieldDefs.AddFieldDef do//Добавляю ещё новое поле begin Name := 'Field2'; DataType := ftInteger;//Тип integer end; with FieldDefs.AddFieldDef do//Добавляю новое поле begin Name := 'Field3'; DataType := ftString;//Тип строка
http://www.vr-online.ru
19
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Size := 30;// Размер строкового поля end; CreateTable;//Создаю новую базу с этими полями Active:=true;//Активирую её. end; end; В самом начале я устанавливаю Table1.Active в False, потому что создание таблицы можно производить только при отключённом компоненте. Если у тебя несколько компонентов ТTable связаны с базой, то нужно отключить их все. Теперь процедура для кнопки 2, где я добавляю индексы. Обрати внимание, что таблица снова отключается Active := False. procedure TForm1.Button2Click(Sender: TObject); begin with Table1 do begin Active := False;//Отключаю таблицу IndexDefs.Clear;//Очищаю текущие индексы with IndexDefs.AddIndexDef do //Добавляю индекс begin Name := '';//Имя индекса. Это главный индекс, поэтому имя пустое Fields := 'Field1';//Индексируемое поле Options := [ixPrimary];//Тип индекса - главный end; with IndexDefs.AddIndexDef do//Добавляю ещё индекс begin Name := 'Fld2Indx'; //Имя индекса Fields := 'Field3'; //Индексируемое поле Options := [ixCaseInsensitive]; //Тип индекса (это вторичный) end; Active := true; //Активирую базу end; end; В моем примере я очищаю текущие индексы. Если ты хочешь сохранить их, то можно не очищать, а просто добавить. Первый индекс я создаю главным. Для этого я указал ixPrimary. Если этот параметр не указан, то индекс будет вторичным. Всё остальное описано в коментариях. Удачи!!! Исходники примера смотри в файле delphi2.zip Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
20
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Delphi (ActiveX): Теория компонентов Мы продолжаем знакомится с Технологией ActiveX. Мы уже познакомились с парой примеров разработки ActiveX приложений, и сегодня мы вдаримся в теорию по самые гланды. Теория будет касатся в основном самостоятельной разработки компонентов. ActiveX - это достаточно сложная для понимания и разработки технология. Она является продолжением развития OLE, которая была пополнена технологией COM и переименована в более ёмкое назнание - ActiveX . Под этим термином скрывается несколько самостоятельных технологий: • • • • •
Формы ActiveForm - мы с ними познакомились в самом начале. Элементы управления Библиотеки Серверы автоматизации Страницы свойств
Всё это объединяется под одним термином ActiveX.
Рис 1. Создание компонентов ActiveX Для разработки приложений с использованием ActiveX нужно иметь достаточно много знаний и навыков. Фирма Borland упростила эту технологию как для понимания, так и для разработки создав свою надстройку - Delphi ActiveX (часто сокращается до DAX). Благодаря DAX ты можешь любой компонент Delphi превратить компонент ActiveX и использовать его в любой другой среде разработки.
http://www.vr-online.ru
21
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Вот именно эти мы и займёмся. Сегодня мы создадим компонент ActiveX с использованием Delphi и надстройки DAX. Хотя о присутствии DAX ты можешь и не знать. Кликай File->New и на закладке ActiveX выбирай ActiveX Control (рис 1). Кликай "ОК". Перед тобой должно открыться окно ActiveX Control Wizard , как на рис 2. Посмотрим на содержимое окна ActiveX Control Wizard : •
VCL Class Name - Имя компонента, который мы хотим превратить в ActiveX. Выбери из списка TPanel.
Рис 2. Мастер компонента ActiveX • • • • • • •
New ActiveX Name - Имя ActiveX компонента (оставь по умолчанию) Implementation Unit - Имя модуля (оставь по умолчанию) Project Name - Имя проекта (оставь по умолчанию) Threading Model - Модель потока. Make Control Licensed - Лицензия компонента. Включай только если захочешь продавать свой компонент. Include Version Information - Включить информацию о версии Include About Box - добавить окно "About"
После нажатия пимпы "ОК", Delphi создаст заготовку для нового твоего компонента, в которой уже готовы к использованию все методы и свойства компонента TPanel. Весь исходный код реализующий компонент будет находится в модуле PanelImpl1.pas. Перейди в него и посмотри. Здесь полно процедур и функций начинающихся словом Get_ или Set_. Зачем они нужны? В ActiveX нет свойств или переменных, весь доступ к компоненту происходит через процедуры или функции, поэтому, чтобы прочитать заголовок панели delphi создал функцию Get_Caption, а чтобы изменить заголовок на новый - Set_Caption(const Value: WideString). Немного позже мы напишем собственную реализацию изменения заголовка. Вся информация о методах компонента находится в библиотеке типов. Чтобы её увидеть выбери Type Library из меню View (рис 3).
http://www.vr-online.ru
22
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Рис 3. Библиотека типов Когда ты вибираешь какой-нибудь элемент библиотеки, в правой стороне окна появляется несколько закладок. На закладке Attributes ты можешь видеть: • • • • • • •
Name - имя объекта GUID - уникальный номер в библиотеке (менять не советую) Version - версия LCID - Идентификатор языка Help String - Краткое описание Help File - Имя Help файла связанного с объектом Help Context - Идентификатор контекста справки
Некоторые из флагов: • • • • • • • • • • •
None - Флаги отсутствуют Restricted - Запретить использование библиотеки в средах программирования макросов Control - В библиотеке находится компонент ActiveX Hidden - Библиотека скрыта от пользователей DispInterface - достук к свойствам и методам производится только через IDispatch. Nonextensible - Если выделен, то реализация интерфейса IDispatch (основной интерфейс ActiveX) будет включать только те свойства и методы, которые показаны в реализации. Dual - Методы и свойства интерфейса передаются и через IDispatch, и таблицу виртуальных методов. OLE Automation - используются только совместимые с автоматизацией типы данных. Source - указывает, что возвращаемое значение является типа VARIANT, являющееся источником событий Bindable - свойство поддерживает связывание данных Request Edit -свойство поддерживает сообщение OnRequestEdit
http://www.vr-online.ru
23
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Небольшое отступление: Интерфейс в библиотеке типов ActiveX, это набор определений свойств и методов. Программа-клиент может получить доступ к интерфейсам (методам) через специальный интерфейс IDispatch,либо через таблицу виртуальных методов. Интерфейс IDispatch позволяет использовать свойства и методы объектов через уникальный идентификатор DispID.
Рис 4 Давай создадим собственный метод. Для этого выдели ветку IPanelX и щёлкни по кнопке . Delphi создаст новое объявление метода Method1 . Переименуй его в SetCap. Delphi создаст процедуру SetCap, которая будет является реальзацией метода SetCap. Перейди на Parametrs (рис 4) и добавь новый параметр для процедуры с именем Caption с типом LPSTR, и Modifier равный [in]. Чтобы изменить Modifier нужно дважды щёлкнуть по нему (рис 5). Здесь тебе доступны:
Рис 5 • • •
In - означает, что метод является процедурай и используется для установки значений Out - Говорит о том, что метод будет считывать значение компонента RetVal - метод будет возвращать значение
Теперь перейди в модуль PanelImpl1.pas и найди процедуру эту SetCap и напиши в ней следующее: procedure TPanelX.SetCap(Caption: PChar);
http://www.vr-online.ru
24
VR-online Journal (Horrific and VR-Team)
Для программистов №8
begin FDelphiControl.Caption:=Caption; end; FDelphiControl указывает на компонент TPanel. С помощью SetCap мы изменяем свойство Caption у TPanel, то есть наш метод делает то же, что и Set_Caption.
Рис 6 Теперь создадим свойство. Кликни по кнопке New Property и выбери "read | write" (рис 6). Delphi создаст два метода: один для чтения свойства, а другой для записи. Переименуй их в MyProp. В модуле PanelImpl1 у тебя появится две новые процедуры: function TPanelX.Get_MyProp: Integer; begin end; procedure TPanelX.Set_MyProp(Value: Integer); begin end; Можешь их реализовать, но я не стал. Мне главное было показать, как это делается. Зарегестрируй новый компонент в системе (Run->Register ActiveX Server). Теперь можешь установить его на палитру компонентов (Component->Import ActiveX Control) и протестировать. После регистрации, компонент будет практически не виден на палитре, потому что у него не будет иконки. Чтобы найти свою панель, перейди на закладку ActiveX и проведи мышкой по палитре компонентов. Самой правой окажется PanelX. Для теста нужно: • • •
Создай новый проект Поставь на форму новый компонент Поставь кнопку и напиши по её событию:
procedure TForm1.Button1Click(Sender: TObject); begin PanelX1.SetCap('привет'); end; Вот и всё. Исходники примера можете взять в файле activex.zip. Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
25
VR-online Journal (Horrific and VR-Team)
Для программистов №8
WinAPI: Функции работы с файлами (Часть 1) Снова новый раздел. Через два месяца журналу исполнится только год, а разделов за это время выросло больше чем в два раза. В этом разделе я начну описывать все известные мне WinAPI функции. Возможно, эти статьи у тебя будут лежать в компютере вместо хелпа. Сегодня нам предстоит увидеть функции для работы с файлами. Хотя они немного устарели и Microsoft требует использования более новых, я всё же их опишу. А вдруг ты будешь читать старые исходники и наткнёшся на них. А если честно, я иногда использую эти функции, потому что они будут ещё долго существовать в Windows для совместимости со старыми прогами.
_lopen Эта функция открывает существующий файл и устанавливает позицию чтения в самое начало. Функция устарела и вместо неё желательно использовать CreateFile (рассмотрим, но в другой раз). Существует в: Win16, Win32, Win NT Для С/С++ объявлена в winbase.h. Для Delphi в модуле windows. Объявление: Для С/С++ HFILE _lopen( LPCSTR lpPathName, // Указатель на строку с именем файла int iReadWrite // Тип доступа ); Для Delphi function _lopen( const lpPathName: LPCSTR; // Указатель на строку с именем файла iReadWrite: Integer // Тип доступа ): HFILE; stdcall; Тип доступа может быть: • • •
OF_READ - для чтения OF_WRITE - для записи OF_READWRITE - для чтения и записи
http://www.vr-online.ru
26
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Если ты хочешь открыть файл с разделяемым доступом, т.е. несколько пользователей смогут открывать один и тот же файл одновременно, то этот параметр сможет принимать значения: • • • • •
OF_SHARE_COMPAT - Открыть файл в разделяемом режиме. В этом случае любые процессы на компьютере смогут тоже открывать этот файл. OF_SHARE_DENY_NONE - Открыть файл без запрещения доступа для других процессов. Если файл уже открыт другим процессом с параметром OF_SHARE_COMPAT, то функция вернёт ошибку. OF_SHARE_DENY_READ - Открыть файл с запретом доступа для других процессов на чтение. Если файл уже открыт другим процессом с параметром OF_SHARE_COMPAT, то функция вернёт ошибку. OF_SHARE_DENY_WRITE - Открыть файл с запретом доступа для других процессов на запись. Если файл уже открыт другим процессом с параметром OF_SHARE_COMPAT, то функция вернёт ошибку. OF_SHARE_DENY_WRITE - Открыть файл с запретом доступа для других процессов на чтение и запись. Если файл уже открыт другим процессом с любым параметром, то функция вернёт ошибку.
Если функция выполнена успешно, то она возвращает указатель на открытый файл. Если произошла ошибка, то функция вернёт HFILE_ERROR. Для получения более полной информации о происшедшей ошибке нужно вызвать функцию GetLastError (вернуть последнюю ошибку). Пример: _lopen('c:\Filename', OF_READWRITE);
_lcreat Функция создаёт и сразу открывает новый файл. Функция устарела и вместо неё желательно использовать CreateFile (рассмотрим, но в другой раз). Существует в: Win16, Win32, Win NT Для С/С++ объявлена в winbase.h. Для Delphi в модуле windows. Объявление: Для С/С++ HFILE _lcreat( LPCSTR lpPathName, // Указатель на имя создаваемого файла int iAttribute // Атрибуты файла ); Для Delphi function _lcreat( const lpPathName: LPCSTR;// Указатель на имя создаваемого файла iAttribute: Integer // Атрибуты файла ): HFILE; stdcall; Атрибуты файла могут быть: • •
0 - Нормальный 1 - Файл только для чтения
http://www.vr-online.ru
27
VR-online Journal (Horrific and VR-Team) • •
Для программистов №8
2 - Скрытый 4 - Системный
Если функция выполнена успешно, то она возвращает указатель на открытый файл. Если произошла ошибка, то функция вернёт HFILE_ERROR. Для получения более полной информации о происшедшей ошибке нужно вызвать функцию GetLastError (вернуть последнюю ошибку). Пример: _creat('c:\Filename', 0);
_lread, _hread Функция читает данные из открытого файла. Функция устарела и вместо неё желательно использовать ReadFile (рассмотрим, но в другой раз). Существует в: Win16, Win32, Win NT Для С/С++ объявлена в winbase.h. Для Delphi в модуле windows. Объявление: Для С/С++ long _hread( HFILE hFile, // Указатель на открытый с помощью _lopen файл LPVOID lpBuffer, // указатель на буфер, куда нужно прочитать long lBytes // длина буфера в байтах ); или UINT _lread( HFILE hFile, LPVOID lpBuffer, UINT uBytes ); Для Delphi function _hread( hFile: HFILE; // Указатель на открытый с помощью _lopen файл lpBuffer: Pointer; // указатель на буфер, куда нужно прочитать lBytes: Longint // длина буфера в байтах ): Longint; или function _lread( hFile: HFILE; lpBuffer: Pointer; uBytes: UINT ): UINT; stdcall; Как видишь, объявлено две функции _lread и _hread. Отличаются они только размерностью параметров. Функция возвращает количество реально прочитанных из файла данных. Если произошла ошибка, то функция вернёт HFILE_ERROR. Для получения более полной информации о происшедшей ошибке нужно вызвать функцию GetLastError (вернуть последнюю ошибку).
http://www.vr-online.ru
28
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Пример: _lread(f, buffer, sizeof(buffer));
_lwrite, _hwrite Функция записывает данные в открытый файл. Функция устарела и вместо неё желательно использовать WriteFile (рассмотрим, но в другой раз). Существует в: Win16, Win32, Win NT Для С/С++ объявлена в winbase.h. Для Delphi в модуле windows. Объявление: Для С/С++ UINT _lread( HFILE hFile, // Указатель на открытый с помощью _lopen файл LPVOID lpBuffer, // указатель на буфер, который нужно записать UINT uBytes // длина буфера в байтах ); или long _hwrite( HFILE hFile, LPCSTR lpBuffer, long lBytes ); Для Delphi function _lwrite( hFile: HFILE; // Указатель на открытый с помощью _lopen файл const lpBuffer: LPCSTR; // указатель на буфер, который нужно записать uBytes: UINT // длина буфера в байтах ): UINT; или function _hwrite( hFile: HFILE; lpBuffer: LPCSTR; lBytes: Longint ): Longint; Как видишь, объявлено две функции _lwrite и _hwrite. Отличаются они только размерностью параметров. Функция возвращает количество реально записанных в файл данных. Если произошла ошибка, то функция вернёт HFILE_ERROR. Для получения более полной информации о происшедшей ошибке нужно вызвать функцию GetLastError (вернуть последнюю ошибку). Пример: _hwrite(f, buffer, sizeof(buffer));
_llseek Функция перемещает позицию чтения/записи в открытом файле. Функция устарела и вместо неё желательно использовать SetFilePointer (рассмотрим, но в другой раз).
http://www.vr-online.ru
29
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Существует в: Win16, Win32, Win NT Для С/С++ объявлена в winbase.h. Для Delphi в модуле windows. Объявление: Для С/С++ LONG _llseek( HFILE hFile, // Указатель на открытый с помощью _lopen файл LONG lOffset, // Количество байт, на которые нужно передвинутся int iOrigin // Позиция, от которой нужно двигаться. ); Для Delphi function _llseek( hFile: HFILE; // Указатель на открытый с помощью _lopen файл lOffset: Longint; // Количество байт, на которые нужно передвинутся iOrigin: Integer // Позиция, от которой нужно двигаться. ): Longint; stdcall; Функция возвращает новую позицию от начала файла. Если произошла ошибка, то функция вернёт HFILE_ERROR. Для получения более полной информации о происшедшей ошибке нужно вызвать функцию GetLastError (вернуть последнюю ошибку). iOrigin может принимать значения: • • •
FILE_BEGIN - двигаться от начала файла на указанное число байт. FILE_CURRENT - двигатся от текущей позиции к концу файла на указанное число байт. FILE_END - двигатся от конца файла к началу на указанное число байт.
Пример: _llseek(f, 10, FILE_END); В этом примере позиция будет установлена на десять байт до конца файла.
_lclose Функция закрывает открытый ранее файл. Функция устарела и вместо неё желательно использовать CloseHandle (рассмотрим, но в другой раз). Существует в: Win16, Win32, Win NT Для С/С++ объявлена в winbase.h. Для Delphi в модуле windows. Объявление: Для С/С++ HFILE _lclose( HFILE hFile, // Указатель на открытый с помощью _lopen файл );
http://www.vr-online.ru
30
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Для Delphi function _lclose( hFile: HFILE; // Указатель на открытый с помощью _lopen файл ): HFILE; stdcall; Если файл закрылся, то возвращается ноль. Если произошла ошибка, то функция вернёт HFILE_ERROR. Для получения более полной информации о происшедшей ошибке нужно вызвать функцию GetLastError (вернуть последнюю ошибку). Пример: _hclose(f);
Пример на Delphi с использованием сегодняшних функций.
var f:HFILE; a:array [0..5] of char; //массив из пяти символов begin f:=_lopen('c:\1.txt',OF_READWRITE);//Открыть файл _lread(f,@a,5); //Прочитать пять символов _llseek(f, 0, FILE_BEGIN);//Вернутся на начало файла _lwrite(f,'VR-online',9); //Записать в файл 9 символов _lClose(f); //Закрыть файл end; Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
31
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Java: Введение в визуализацию Начиная с сегодняшнего дня, мы переходим в визуальную среду разработки JBuilder. Теперь мы будем получать истинное удовольствие от программирования на Java. Самое вкусное во всём этом - фирма Inprise/Borland теперь сделала доступным и бесплатным базовый набор JBuilder. На первых порах его нам будет достаточно. С помощью базовых средств можно запрограммировать очень много, хотя для больших и сложных проектов понадобится полная версия JBulder. Сегодня нам предстоит познакомится со средой программирования. Запусти JBuilder и посмотри на этого красавца. У меня стоит версия 3,5 и я просто тащусь от его интерфейса. Если ты программируешь на Delphi, то многие вещи тебе будут уже знакомы. Давай посмотрим на менюшки. На рисунке 1. ты можешь наблюдать меню File: • • • • • • • • • • • • • • • • • • •
New...Создать новый файл New project... Создать новый проект New folder... Создать новую папку в проекте New class... Создать новый класс Open Ну это и в Африке "открыть" Reopen Переоткрыть Close project: ... Закрыть проект ... Close Просто закрыть Close All Закрыть на хрен Revert Вернуть Save project Сохранить проект Save project as Сохранить проект под новым именем Save Просто сохранить Save as Сохранить файл под новым именем Save all Cохранить всё Rename Переименовать Page layout Выравнивание страницы Print Печать Exit Удачи
Это было меню File . Все остальные менюшки такие же, как и всегда. После загрузки, JBuilder создаёт проект по умолчанию. Закрой его с помощью File -> Close project: ... . Рис 1. Меню File
http://www.vr-online.ru
32
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Рис 2. Новый проект File Теперь мы сами создадим новый проект, который нам нужен. Для этого выбери пункт New из меню "File". Перед тобой откроется окно как на рисунке 2. Выбери Application (приложение) и жми на ОК. И снова перед тобой откроется окно. В этом окне ты должен ввести имя проекта с учётом его расположений (т.е. полный путь к имени проекта). Оставь поумолчанию, потому что мы всё равно тренируемся. Жми Next и ты увидешь окно, как на рисунке 3 (я не буду приводить все скриншоты окон, потому что эта страница и так перегружена графикой). И это окно не последнее. Приготовся. Щас покатит столько глупых вопросов, что на всю жизнь хватит.
http://www.vr-online.ru
33
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Рис 3. Создание нового проекта (Шаг 2) File Здесь ты вводишь чисто информативные вещи. Дави Finish и ты снова получишь окно. В нём ты должен ввести имя пакета и имя класса. Назови их как угодно, я оставил поумолчанию. Нажимай Next и получай последнее в этом сезоне окно (рис 4).
Рис 3. Параметры проекта (Шаг 2) File Здесь ты должен ввести имя класса и заголовок окна. Ниже ты выбираешь, что должен сгенерировать JBuilder (меню, Status Bar, окно "О программе" ...). Я выделил всё, чтобы первый пример был насыщеным. Всё. Жми Finish и получай, что заказывал. Нажми F9 и наслаждайся первым примером. А я закругляюсь, потому что страничка и так перегружена графикой и я не представляю сколько времени она будет у тебя грузится. Удачи. В следующий раз мы продвинимся дальше. Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
34
VR-online Journal (Horrific and VR-Team)
Для программистов №8
SQL: Работа с полями До сегодняшнего дня мы занимались выборками из базы данных. Я рассказал тебе практически все возможные приёмы для формировании данных из таблиц. Теперь пора научиться вставлять, удалять и модифицировать строки. Для всего этого в SQL есть три магических оператора: INSERT (вставить), UPDATE (модифицировать), DELETE (удалить). Рассмотрим вставку строки, для этого используется простейшая конструкция: INSERT INTO Имя Таблицы VALUES (Значение 1, Значение 2, и т.д.); Это общий вид. После оператора VALUES идёт перечисление всех полей строки. Теперь взглянём на конкретный пример: INSERT INTO User1 VALUES ('Иванов', 'Сергей', 34); Этой командой мы вставили строку и присвоили значения полям. В моей таблицы три поля: первые два поля строковые (Фамилия и Имя), последнее поле - целое число (возраст). Типы данных обязаны совпадать с теми, что установлены в таблицы, иначе секир башка твоему запросу. А если ты не хочешь задавать все поля? Тогда ты можешь оставить их пустыми с помощью NULL: INSERT INTO User1 VALUES ('Иванов', NULL, 34); Как видишь, второе поле я оставил пустым и в него не будет заноситься значение. А если у тебя таблица с большим количеством полей и ты хочешь заполнить только два из них? Неужели придется всем остальным полям ставить значение NULL? Нет. SQL это достаточно продуманный язык и в нём есть на этот случай удобная вещичка: INSERT INTO User1 (Family, Age) VALUES ('Иванов', 35); После конструкции INSERT INTO и имени базы я поставил скобки, где перечислил поля, которые необходимо заполнить (Фамилия и Возраст). В скобках после слова VALUES я перечисляю эти поля в той же последовательности, в которой перечислил перед этим (сначала фамилия, а потом возраст). Теперь представь, что ты хочешь сохранить результат запроса SELECT в отдельной таблице. Для этого в SQL всё уже предусмотрено. Тебе нужно только написать:
http://www.vr-online.ru
35
VR-online Journal (Horrific and VR-Team)
Для программистов №8
INSERT INTO User1 SELECT * FROM User2 WHERE Age=10 В этом примере сначала выполнится запрос SELECT: SELECT * FROM User2 WHERE Age=10 После его выполнения, результат будет занесён в таблицу User1. Ну как? Только не забудь, что количество столбцов в запросе и результирующей таблицы должно быть одинаково. А самое главное, это чтобы тип данных совпадал, иначе Гитлер капут. Усложняем задачу. Теперь рассмотрим такой запрос: INSERT INTO User1(Name,Age) SELECT Name,Age FROM User2 WHERE Age=10 Теперь в таблицу User1 будут перенесены только два столбца (имя и возраст). Здесь действуют те же ограничения - количество полей должно быть одинаково. Но есть и ещё одно - поля должны быть перечислены в таком порядке, чтобы типы и длина полей совпадали. У меня они перечислены так, что первое поле строковое, а второе целое число. Двигаемся дальше. Мы смогли добавить строки, но надо и научиться изменять данные. Для этого нам доступна команда UPDATE . Сразу же попробуем взглянуть на пример: UPDATE User1 SET age=65 Первая строка говорит о том, что нам надо обновить базу User1. Вторая строка начинается с оператора SET (установить). После этого я пишу поле, которое хочу обновить и присваиваю ему значение. Этот маленький пример установит поле age у всех строк в значение 65. Если тебе нужно обновить только определённые строки, то ты должен написать так: UPDATE User1 SET age=65 WHERE Name LIKE 'Вася' Этот запрос установит значение 65 в поле AGE только тем строкам, в которых поле Name равно "Вася". И снова усложняем себе жизнь. UPDATE User1 SET age=age+1 Этот запрос увеличит во всех строках таблицы поле Age на единицу.
http://www.vr-online.ru
36
VR-online Journal (Horrific and VR-Team)
Для программистов №8
И наконец, обновление сразу нескольких полей: UPDATE User1 SET age=age+1, Name='Иван' WHERE Family LIKE 'Сидоров' Этот запрос увеличит поле Age на единицу и установит поле Name в "Иван" во всех строках, где поле Family равно "Сидоров". С обновлением полей покончено, теперь мы переходим к удалению строк из таблицы. Для этого есть команда DELETE : DELETE FROM User1 Всё очень просто, эта конструкция удаляет абсолютно все строки из таблицы User1. Можно сказать, что этим мы очищаем таблицу. Теперь рассмотрим другой пример: DELETE FROM User1 WHERE Age=10 Этот пример удаляет только те строки, в которых поле Age равно 10. Всё. Мы и так сегодня изучили достаточно много, поэтому пора заканчивать. Удачи!!! Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
37
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Форматы файлов: WAV – звуковое сопровождение Этот формат наиболее распространён в системе windows. В большинстве это связано с тем, что его структура очень удобна для использования в API функциях. А самое главное это то, что данные в файле могут находиться как в сжатом, так и несжатом виде. Это даёт возможность его использования на все случаи жизни. Помимо звуковых данных, ты можешь засунуть сюда всё, что угодно. Примером может быть любая информация о создателях файла или графические картинки. Короче гворя, WAV формат универсален. Единственный "недостаток", файл не может быть более 4Гб. Слово "недостаток" я взял в кавычки, потому что этого размера тебе будет достаточно для записи трёх часов музыки в отличном качестве и без сжатия. Я не думаю, что у тебя будут такие громадные файлы. Данный формат относится к разряду RIFF (Resource Interchange File Formats). Чтобы ты понимал, к этому разряду относится также и знаменитый видеоформат AVI. Теперь переходим к структуре. В WAV файле может храниться один или несколько блоков данных. Каждый блок может содержать ещё несколько блоков. Так получается дерево звуковых данных. Каждый блок имеет свой заголовок. Заголовок состоит из: • •
Идентификатор фрагмента (4 байта). Размер звуковых данных (4 байта).
Идентификатор может содержать значения: RIFF, DATA, .TXT и т.д. В статьях о программировании звука нас будут интересовать только блоки RIFF и data. Теперь посмотрим на структуру WAV файла, с которым мы будем работать при программировании звука: • • • • • • • •
4 байта - идентификатор файла. В нашем случае всегда RIFF. 4 байта - размер данных. 8 байт - снова идентификатор. Первые 7 байт равны WAVE fmt. Последний в основном пробел. 4 байта - размер последующих данных. WAVEFORMATEX - структура, описывающая хранящиеся звуковые данные. 4 байта - идентификатор. Равен data. 4 байта - размер звуковых данных. Сами данные.
Это структура файла. Нас ещё интересует структура WAVEFORMATEX: PWaveFormatEx = ^TWaveFormatEx; tWAVEFORMATEX = packed record wFormatTag: Word; nChannels: Word; nSamplesPerSec: DWORD;
http://www.vr-online.ru
38
VR-online Journal (Horrific and VR-Team)
Для программистов №8
nAvgBytesPerSec: DWORD; nBlockAlign: Word; wBitsPerSample: Word; cbSize: Word; end; • • • • • • •
wFormatTag - Формат звуковых данных. Мы будем использовать в основном WAVE_FORMAT_PCM. nChannels - Количество каналов (1- моно, 2 - стерео). nSamplesPerSec - Частота дискретизации (возможны значения 8000, 11025. 22050 и 44100). nAvgBytesPerSec - Количество байт в секунду. Для WAVE_FORMAT_PCM это является результатом nSamplesPerSec* nBlockAlign. nBlockAlign - Выравнивание блока. Для WAVE_FORMAT_PCM равен wBitsPerSample/8* nChannels wBitsPerSample -Количество бит в одной выборке. Для WAVE_FORMAT_PCM может быть 8 или 16. cbSize - Размер дополнительной информации, которая располагается после структуры. Если ничего нет, то должен быть 0.
Если ты читал первую статью про программирование звука в моём журнале, то наверно уже заметил, что для воспроизведения звуковых данных нам нужна точно такая же структура. Это очень упрощает нашу прогу. Достаточно прочитать WAVEFORMATEX и звуковые данные и отправить всё это драйверу. Никаких преобразований делать не надо. Всё. Осталось только воспользоваться полученными знаниями. Удачного тебе саунда. Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
39
VR-online Journal (Horrific and VR-Team)
Для программистов №8
Программирование звука: Пример воспроизведения Мы уже знаем основные функции воспроизведения звуковых данных, знаем формат WAV файла, пора бы написать реальный пример. Сегодня мы выведем звуковые данные в колонки. Пример будет чисто демонстрационным и работает он только в Win9x. В NT наша прога непокатит. На рисунке 1 показана форма сегодняшней проги. Я понимаю, что ты не станешь писать всё что я говорю своими руками, но я всё же объясню внутренности примера. Это тебе пригодится. Самое главное - подключить модуль mmsystem в раздел uses. В этом модуле объявлены все мультимедиа функции. А теперь переходим к примеру. Он достаточно большой и его изучение отнимет очень много времени. Если ты сможешь с ним разобратся, то поймёшь достаточно много тонкостей в программировании. Я здесь использовал очень много приёмов, которые мы ещё не обсуждали, но в следующем номере я постараюсь восполнить эту дыру. А начнём мы как всегда с загрузки, а именно с обработчика OnShow: procedure TSounderForm.FormShow(Sender: TObject); begin EnableBut(false); FillDevice; RestartPlay:=false; end;
Рис 1. Форма
EnableBut - моя функция, которая делает недоступными кнопки воспроизведения, потому что ещё не загружен звуковой файл. Как только пользователь выберет WAV файл, мы снова сделаем эти кнопки доступными. FillDevice - моя функция, которая определяет возможности звуковухи. Давай рассмотрим её подробнее: procedure TSounderForm.FillDevice; var pwoc:TWaveOutCaps;// Объявляю переменную типа TWaveOutCaps begin //Запросить свойства звуковухи waveOutGetDevCaps(WAVE_MAPPER, @pwoc, sizeof(pwoc)); // Если звуковая поддерживает изменение звука, то ... if (pwoc.dwSupport and WAVECAPS_VOLUME)>0 then begin
http://www.vr-online.ru
40
VR-online Journal (Horrific and VR-Team)
Для программистов №8
// Делаю доступными регуляторы громкости. VolumeLBar.Enabled:=true; VolumeRBar.Enabled:=true; end; end; Здесь я использовал пока ещё неизвестную нам функцию waveOutGetDevCaps. Но это только пока. Сейчас мы разберёмся с этим монстром. Она выглядит так: Для С/С++ MMRESULT waveOutGetDevCaps( UINT uDeviceID, //Указатель на устройство воспроизведения LPWAVEOUTCAPS pwoc,//Структура, куда будут записаны данные UINT cbwoc //Размер структуры ); Для Delphi function waveOutGetDevCaps( uDeviceID: UINT; //Указатель на устройство воспроизведения lpCaps: PWaveOutCaps; //Структура, куда будут записаны данные uSize: UINT //Размер структуры ): MMRESULT; stdcall; Второй параметр - структура WAVEOUTCAPS. Она выглядит так: Для С/С++ typedef struct { WORD wMid; //Идентификатор производителя WORD wPid; //Идентификатор устройства MMVERSION vDriverVersion; //Версия драйвера CHAR szPname[MAXPNAMELEN];//Название устройства DWORD dwFormats; //Поддерживаемые форматы WORD wChannels; //Количество каналов WORD wReserved1; //Зарезервировано DWORD dwSupport; //Свойства } WAVEOUTCAPS; Для Delphi PWaveOutCapsA = ^TWaveOutCapsA; PWaveOutCapsW = ^TWaveOutCapsW; PWaveOutCaps = PWaveOutCapsA; {$EXTERNALSYM tagWAVEOUTCAPSA} tagWAVEOUTCAPSA = record wMid: Word; //Идентификатор производителя wPid: Word; //Идентификатор устройства vDriverVersion: MMVERSION; //Версия драйвера szPname: array[0..MAXPNAMELEN-1] of AnsiChar; //Название устройства dwFormats: DWORD; //Поддерживаемые форматы wChannels: Word; //Количество каналов dwSupport: DWORD; //Свойства end; Самым интересным для нас является последний параметр - dwSupport . Он может принимать значения: •
WAVECAPS_LRVOLUME - поддерживается изменение правого и левого каналов.
http://www.vr-online.ru
41
VR-online Journal (Horrific and VR-Team) • • • • •
Для программистов №8
WAVECAPS_PITCH - Поддерживаются подтяжки WAVECAPS_PLAYBACKRATE - поддерживается playback rate. WAVECAPS_SYNC - драйвер сихронный и будет блокировать работу пока играет музыка. WAVECAPS_VOLUME - поддержка громкости. WAVECAPS_SAMPLEACCURATE - возвращает позицию.
Параметр wChannels может быть 1 (для моно) и 2 (для стерео). Параметр dwFormats может быть комбинацией следующих значений: • • • • • • • • • • • •
WAVE_FORMAT_1M08 - 11.025 kHz, mono, 8-bit WAVE_FORMAT_1M16 - 11.025 kHz, mono, 16-bit WAVE_FORMAT_1S08 - 11.025 kHz, stereo, 8-bit WAVE_FORMAT_1S16 - 11.025 kHz, stereo, 16-bit WAVE_FORMAT_2M08 - 22.05 kHz, mono, 8-bit WAVE_FORMAT_2M16 - 22.05 kHz, mono, 16-bit WAVE_FORMAT_2S08 - 22.05 kHz, stereo, 8-bit WAVE_FORMAT_2S16 - 22.05 kHz, stereo, 16-bit WAVE_FORMAT_4M08 - 44.1 kHz, mono, 8-bit WAVE_FORMAT_4M16 - 44.1 kHz, mono, 16-bit WAVE_FORMAT_4S08 - 44.1 kHz, stereo, 8-bit WAVE_FORMAT_4S16 - 44.1 kHz, stereo, 16-bit
Теперь перейдём к загрузке wav файла. По нажатию кнопки "Открыть" происходит следующее: procedure TSounderForm.OpenButtonClick(Sender: TObject); begin //Сначала, на всякий случай остановим воспроизведение StopButtonClick(nil); DataSize:=0; // Обнуляю размер данных if OpenDialog1.Execute then //Показать окно открытия файла OpenWaveFile; //Если файл выбран, то открыть его WavFileName:=OpenDialog1.FileName; //Сохраняем имя файла //Изменяем заголовок окна. Caption:='Sounder - '+ExtractFileName(WavFileName); //Показываю параметры звуковых данных. if wfx.nChannels=1 then ChanelsBox.Text:='Моно' else ChanelsBox.Text:='Стерео'; FreqBox.Text:=IntToStr(wfx.nSamplesPerSec); BitsBox.Text:=IntToStr(wfx.wBitsPerSample); Label7.Caption:=IntToStr(wfx.nAvgBytesPerSec); end; Здесь можно разобратся со всем происходящим по коментариям. В принципе, ничего особенного не происходит. Самое интересное будет в процедуре OpenWaveFile. Вот её мы сейчас и будем разглядывать. Если ты прочитал предыдущую статью о программировании звука и статью о формате файла WAV, то и она покажется тебе достаточно лёгкой. procedure TSounderForm.OpenWaveFile; var
http://www.vr-online.ru
42
VR-online Journal (Horrific and VR-Team)
Для программистов №8
hFile:THandle; Str:array [0..255] of char; TempStr:String; BytesReaded,Size:DWORD; begin //Если мы уже открывали WAV файл, то закрыть предыдущий if w_hData<>0 then CloseHandle(w_hData); //Открыть файл для чтения. Это достаточно продвинутая функция и мы //познакомимся с ней отдельно. hFile:=CreateFile(PChar(OpenDialog1.FileName),GENERIC_READ, FILE_SHARE_READ,nil,OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE,0); //Если файл открыт нормально, то продолжаем. if hFile=INVALID_HANDLE_VALUE then exit; //С TRY мы познакомимся в следующий раз //Пока скажу, что он работает, как Begin, а вместо end //нужно ставить finally + операторы + end; //или except + операторы + end; try //Получаем размер файлы с помощью GetFileSize wFileSize:=GetFileSize(hFile,nil); try //Устанавливаем позицию в файле на 8 байт от начала SetFilePointer(hFile,8,nil,FILE_BEGIN); //Прочитаем начиная от этой позиции 8 байт ReadFile(hFile,Str,8,BytesReaded,nil); Str[7]:=#0;//Обнуляю седьмой байт TempStr:=Str;//Перегоняю результат в строку //Если прочитанная строка не равна 'WAVEfmt', то это не WAV if TempStr<>'WAVEfmt' then begin //Вывожу сообщение об ошибке Application.MessageBox('Ошибка заголовка','Error', MB_OK+MB_ICONINFORMATION); CloseHandle(hFile);//Закрываю файл exit; //Выход из процедуры end; //Читаем размер следующего блока ReadFile(hFile,Size,Sizeof(Size),BytesReaded,nil); //Обнуляем структуру wfx ZeroMemory(@wfx,sizeof(wfx)); //Если размер следующего блока равен размеру структуры, //То читаем структуру. Иначе выдаём ошибку if Size=sizeof(PCMWAVEFORMAT) then begin ReadFile(hFile,wfx,Size,BytesReaded,nil); end else begin MessageBox(Handle, 'Формат не поддерживается', 'Error', MB_OK+MB_ICONEXCLAMATION); exit; end; wfx.cbSize:=0; ReadFile(hFile,Str,4,BytesReaded,nil);
http://www.vr-online.ru
43
VR-online Journal (Horrific and VR-Team)
Для программистов №8
//Читаем размер следующего блока ReadFile(hFile,DataSize,4,BytesReaded,nil); //Далее идут звуковые данные, поэтому сохраняем текущую позицию //в DataOffset, чтобы можно было сразу перейти к данным. DataOffset:=SetFilePointer(hFile,0,nil,FILE_CURRENT); //Создаём образ файла в памяти с помощью CreateFilemapping w_hData:=CreateFilemapping(hFile,nil,PAGE_READONLY,0, Integer(wFileSize),nil); //Закрываем файл CloseHandle(hFile); hFile:=INVALID_HANDLE_VALUE; except Application.MessageBox('Формат не поддерживается','Error', MB_OK+MB_ICONINFORMATION); CloseHandle(hFile); hFile:=INVALID_HANDLE_VALUE; end; finally if hFile<>INVALID_HANDLE_VALUE then CloseHandle(hFile); if DataSize>0 then begin EnableBut(true); InitSoundControls; end else EnableBut(false); end; end; WAV файл загружен. Теперь переходим к воспроизведению. По нажатию кнопки "Играть" происходит следующее: procedure TSounderForm.PlayButtonClick(Sender: TObject); begin //Если была нажата пауза, то продолжить воспроизведение. if ((w_Pause)and (w_hPlay<>0)) then begin waveOutRestart(w_hPlay); w_Pause:=not w_Pause; exit; end; //Если данные воспроизводятся, то остановить вывод if w_hPlay<>0 then StopButtonClick(nil); //Включить таймер SoundTimer.Enabled:=true; w_Pause:=false; w_Restart:=false; w_Stop:=false; w_PlayPosition:=PositionBar.Position*1000; w_RestartPosition:=PositionBar.Position*1000; //Устанавливаю устройство поумолчанию. w_DeviceID:=Integer(WAVE_MAPPER); //Запускаю поток воспроизведения данных. CyDSounder1:=TSoundPlayer.Create(true);
http://www.vr-online.ru
44
VR-online Journal (Horrific and VR-Team)
Для программистов №8
//Устанавливаю высокий приоритет CyDSounder1.Priority:=tpHigher; CyDSounder1.Resume; end; При запуске потока я вызываю только одну процедуру, которая будет отвечать за вывод звуковых данных: procedure TSoundPlayer.Execute; begin Process; end; Давай мельком посмотрим на эту процедуру: procedure TSoundPlayer.Process; const DEFAULT_BLOCKSIZE=64*1024; //Размер 1-го блока var BlockSize,ii,shift,size:Integer; si:TSystemInfo; curHdr:WAVEHDR; ofs:Int64; p:PSoundArray; begin BlockSize:=DEFAULT_BLOCKSIZE; GetSystemInfo(si); //Получаю системную информацию ZeroMemory(@Header,sizeof(Header)); //Обнуляю заголовок hEvent:=CreateEvent(nil,false,false,nil); try //Открываю звуковуху WaveOutCheck(waveOutOpen(@SounderForm.w_hPlay, SounderForm.w_DeviceID, @SounderForm.wfx, hEvent, 0,CALLBACK_EVENT)); //Выделяю память для данных Header[0].lpData:=VirtualAlloc(nil,round((BlockSize*2+ Integer(si.dwPageSize)-1)/si.dwPageSize*si.dwPageSize), MEM_RESERVE+MEM_COMMIT,PAGE_READWRITE); if Header[0].lpData=nil then exit; //Выделенную память разделяю на два заголовка Header[1].lpData:=Header[0].lpData+BlockSize; Header[0].dwBufferLength:=BlockSize; Header[1].dwBufferLength:=BlockSize; //Подготавливаю заголовки waveOutCheck(waveOutPrepareHeader(SounderForm.w_hPlay, @Header[0],sizeof(waveHDR))); waveOutCheck(waveOutPrepareHeader(SounderForm.w_hPlay, @Header[1],sizeof(waveHDR))); ii:=0; if SounderForm.DataSize<=BlockSize then ResetEvent(hEvent); //Запускаю цикл воспроизведения while SounderForm.w_PlayPositionSounderForm.DataSizeSounderForm.w_PlayPosition then BlockSize:=SounderForm.DataSize-SounderForm.w_PlayPosition; //Расчитываю размер данных curhdr.dwBufferLength:=BlockSize;
http://www.vr-online.ru
45
VR-online Journal (Horrific and VR-Team)
Для программистов №8
ofs:=SounderForm.w_PlayPosition+SounderForm.DataOffset; shift:=(ofs mod si.dwAllocationGranularity); ofs:=ofs-shift; size:=(BlockSize+ofs+si.dwPageSize-1); size:=size-(size mod Integer(si.dwPageSize)); if size>SounderForm.wFileSize-ofs then size:=SounderForm.wFileSize-ofs; //Загружаю из памяти файла звуковые данные p:=MapViewOfFile(SounderForm.w_hData,FILE_MAP_READ, ofs shr 32 and $FFFFFFFF,ofs and $FFFFFFFF, Size); try //Копирую в заголовок CopyMemory(curhdr.lpData, @p[shift], min(size-shift,BlockSize)); finally UnMapViewOfFile(p); end; //Воспроизвожу. waveOutCheck(waveOutWrite(SounderForm.w_hplay,@curhdr,sizeof(waveHDR))); //Ожидаю окончания воспроизведения if (WAIT_TIMEOUT=WaitForSingleObject(hEvent,10000)) then begin if not SounderForm.w_Pause then begin MessageBox(SounderForm.Handle,'Ошибка воспроизведения','Ошибка', MB_OK+MB_ICONINFORMATION); break; end else WaitForSingleObject(hEvent,INFINITE); end; //Если нажат стоп, то выход if SounderForm.w_Stop then ClosePlay; //Если отпущена пауза, то востановить воспроизведение if SounderForm.w_Restart then begin waveOutReset(SounderForm.w_hPlay); SounderForm.w_Restart:=false; SounderForm.w_PlayPosition:=SounderForm.w_RestartPosition; BlockSize:=DEFAULT_BLOCKSIZE; if SounderForm.DataSize>BlockSize then SetEvent(hEvent); continue; end; //Новая позиция для чтения следующих данных SounderForm.w_PlayPosition:=SounderForm.w_PlayPosition+BlockSize; end; finally //Ожидаю окончания воспроизведения WaitForSingleObject(hEvent,20000); //Закрываю воспроизведение ClosePlay; end; end; На сегодня хватит. Я просто уверен, что ты не до конца понял, что здесь происходит. Пример получился слишком насыщеный новыми вещами. В следующем месяце я постараюсь объяснить как можно больше из того, что здесь было нового. А пока что наслождайся музыкой. Copyright © Фленов Михаил aka Horrific
http://www.vr-online.ru
46