Что такое протокол межсетевого взаимодействия IP?
Что такое протокол межсетевого взаимодействия IP?
Основу транспортных средств стека протоколов TCP/IP составляет протокол межсетевого взаимодействия - Internet Protocol (IP). К основным функциям протокола IP относятся:
перенос между сетями различных типов адресной информации в унифицированной форме,
сборка и разборка пакетов при передаче их между сетями с различным максимальным значением длины пакета.
Формат пакета IP
Пакет IP состоит из заголовка и поля данных. Заголовок пакета имеет следующие поля:
Поле Номер версии (VERS) указывает версию протокола IP. Сейчас повсеместно используется версия 4 и готовится переход на версию 6, называемую также IPng (IP next generation).
Поле Длина заголовка (HLEN) пакета IP занимает 4 бита и указывает значение длины заголовка, измеренное в 32-битовых словах. Обычно заголовок имеет длину в 20 байт (пять 32-битовых слов), но при увеличении объема служебной информации эта длина может быть увеличена за счет использования дополнительных байт в поле Резерв (IP OPTIONS).
Поле Тип сервиса (SERVICE TYPE) занимает 1 байт и задает приоритетность пакета и вид критерия выбора маршрута. Первые три бита этого поля образуют подполе приоритета пакета (PRECEDENCE). Приоритет может иметь значения от 0 (нормальный пакет) до 7 (пакет управляющей информации). Маршрутизаторы и компьютеры могут принимать во внимание приоритет пакета и обрабатывать более важные пакеты в первую очередь. Поле Тип сервиса содержит также три бита, определяющие критерий выбора маршрута. Установленный бит D (delay) говорит о том, что маршрут должен выбираться для минимизации задержки доставки данного пакета, бит T - для максимизации пропускной способности, а бит R - для максимизации надежности доставки.
Поле Общая длина (TOTAL LENGTH) занимает 2 байта и указывает общую длину пакета с учетом заголовка и поля данных.
Поле Идентификатор пакета (IDENTIFICATION) занимает 2 байта и используется для распознавания пакетов, образовавшихся путем фрагментации исходного пакета. Все фрагменты должны иметь одинаковое значение этого поля.
Поле Флаги (FLAGS) занимает 3 бита, оно указывает на возможность фрагментации пакета (установленный бит Do not Fragment - DF - запрещает маршрутизатору фрагментировать данный пакет), а также на то, является ли данный пакет промежуточным или последним фрагментом исходного пакета (установленный бит More Fragments - MF - говорит о том пакет переносит промежуточный фрагмент).
Поле Смещение фрагмента (FRAGMENT OFFSET) занимает 13 бит, оно используется для указания в байтах смещения поля данных этого пакета от начала общего поля данных исходного пакета, подвергнутого фрагментации. Используется при сборке/разборке фрагментов пакетов при передачах их между сетями с различными величинами максимальной длины пакета.
Поле Время жизни (TIME TO LIVE) занимает 1 байт и указывает предельный срок, в течение которого пакет может перемещаться по сети. Время жизни данного пакета измеряется в секундах и задается источником передачи средствами протокола IP. На шлюзах и в других узлах сети по истечении каждой секунды из текущего времени жизни вычитается единица; единица вычитается также при каждой транзитной передаче (даже если не прошла секунда). При истечении времени жизни пакет аннулируется.
Идентификатор Протокола верхнего уровня (PROTOCOL) занимает 1 байт и указывает, какому протоколу верхнего уровня принадлежит пакет (например, это могут быть протоколы TCP, UDP или RIP).
Контрольная сумма (HEADER CHECKSUM) занимает 2 байта, она рассчитывается по всему заголовку.
Поля Адрес источника (SOURCE IP ADDRESS) и Адрес назначения (DESTINATION IP ADDRESS) имеют одинаковую длину - 32 бита, и одинаковую структуру.
Поле Резерв (IP OPTIONS) является необязательным и используется обычно только при отладке сети. Это поле состоит из нескольких подполей, каждое из которых может быть одного из восьми предопределенных типов. В этих подполях можно указывать точный маршрут прохождения маршрутизаторов, регистрировать проходимые пакетом маршрутизаторы, помещать данные системы безопасности, а также временные отметки. Так как число подполей может быть произвольным, то в конце поля Резерв должно быть добавлено несколько байт для выравнивания заголовка пакета по 32-битной границе.
Максимальная длина поля данных пакета ограничена разрядностью поля, определяющего эту величину, и составляет 65535 байтов, однако при передаче по сетям различного типа длина пакета выбирается с учетом максимальной длины пакета протокола нижнего уровня, несущего IP-пакеты. Если это кадры Ethernet, то выбираются пакеты с максимальной длиной в 1500 байтов, умещающиеся в поле данных кадра Ethernet.
Управление фрагментацией
Протоколы транспортного уровня (протоколы TCP или UDP), пользующиеся сетевым уровнем для отправки пакетов, считают, что максимальный размер поля данных IP-пакета равен 65535, и поэтому могут передать ему сообщение такой длины для транспортировки через интерсеть. В функции уровня IP входит разбиение слишком длинного для конкретного типа составляющей сети сообщения на более короткие пакеты с созданием соответствующих служебных полей, нужных для последующей сборки фрагментов в исходное сообщение.
В большинстве типов локальных и глобальных сетей определяется такое понятие как максимальный размер поля данных кадра или пакета, в которые должен инкапсулировать свой пакет протокол IP. Эту величину обычно называют максимальной единицей транспортировки - Maximum Transfer Unit, MTU. Сети Ethernet имеют значение MTU, равное 1500 байт, сети FDDI - 4096 байт, а сети Х.25 чаще всего работают с MTU в 128 байт.
Работа протокола IP по фрагментации пакетов в хостах и маршрутизаторах иллюстрируется рисунком 4.1.
Пусть компьютер 1 связан с сетью , имеющей значение MTU в 4096 байтов, например, с сетью FDDI. При поступлении на IP-уровень компьютера 1 сообщения от транспортного уровня размером в 5600 байтов, протокол IP делит его на два IP-пакета, устанавливая в первом пакете признак фрагментации и присваивая пакету уникальный идентификатор, например, 486. В первом пакете величина поля смещения равна 0, а во втором - 2800. Признак фрагментации во втором пакете равен нулю, что показывает, что это последний фрагмент пакета. Общая величина IP-пакета составляет 2800+20 (размер заголовка IP), то есть 2820 байтов, что умещается в поле данных кадра FDDI.
Далее компьютер 1 передает эти пакеты на канальный уровень К1, а затем и на физический уровень Ф1, который отправляет их маршрутизатору, связанному с данной сетью .
Маршрутизатор видит по сетевому адресу, что прибывшие два пакета нужно передать в сеть 2, которая имеет меньшее значение MTU, равное 1500. Вероятно, это сеть Ethernet. Маршрутизатор извлекает фрагмент транспортного сообщения из каждого пакета FDDI и делит его еще пополам, чтобы каждая часть уместилась в поле данных кадра Ethernet. Затем он формирует новые пакеты IP, каждый из которых имеет длину 1400 + 20 = 1420 байтов, что меньше 1500 байтов, поэтому они нормально помещаются в поле данных кадров Ethernet.
В результате в компьютер 2 по сети Ethernet приходит четыре IP-пакета с общим идентификатором 486, что позволяет протоколу IP, работающему в компьютере 2, правильно собрать исходное сообщение. Если пакеты пришли не в том порядке, в котором были посланы, то смещение укажет правильный порядок их объединения.
Отметим, что IP-маршрутизаторы не собирают фрагменты пакетов в более крупные пакеты, даже если на пути встречается сеть, допускающая такое укрупнение. Это связано с тем, что отдельные фрагменты сообщения могут перемещаться по интерсети по различным маршрутам, поэтому нет гарантии, что все фрагменты проходят через какой-либо промежуточный маршрутизатор на их пути.
При приходе первого фрагмента пакета узел назначения запускает таймер, который определяет максимально допустимое время ожидания прихода остальных фрагментов этого пакета. Если таймер истекает раньше прибытия последнего фрагмента, то все полученные к этому моменту фрагменты пакета отбрасываются, а в узел, пославший исходный пакет, направляется сообщение об ошибке с помощью протокола ICMP.
Маршрутизация с помощью IP-адресов
Рассмотрим теперь принципы, на основании которых в сетях IP происходит выбор маршрута передачи пакета между сетями.
Сначала необходимо обратить внимание на тот факт, что не только маршрутизаторы, но и конечные узлы - компьютеры - должны принимать участие в выборе маршрута. Пример, приведенный на рисунке 4.2, демонстрирует эту необходимость. Здесь в локальной сети имеется несколько маршрутизаторов, и компьютер должен выбирать, какому из них следует отправить пакет.
Длина маршрута может существенно измениться в зависимости от того, какой маршрутизатор выберет компьютер для передачи своего пакета на сервер, расположенный, например, в Германии, если маршрутизатор 1 соединен выделенной линией с маршрутизатором в Копенгагене, а маршрутизатор 2 имеет спутниковый канал, соединяющий его с Токио.
В стеке TCP/IP маршрутизаторы и конечные узлы принимают решения о том, кому передавать пакет для его успешной доставки узлу назначения, на основании так называемых таблиц маршрутизации (routing tables).
Следующая таблица представляет собой типичный пример таблицы маршрутов, использующей IP-адреса сетей:
Адрес сети
назначения Адрес следующего маршрутизатора Номер выходного
порта Расстояние до
сети назначения
56.0.0.0198.21.17.7 1 20
56.0.0.0 213.34.12.4. 2 130
116.0.0.0 213.34.12.4 2 1450
129.13.0.0 198.21.17.6 1 50
198.21.17.0 - 2 0
213. 34.12.0 - 1 0
default 198.21.17.7 1 -
В этой таблице в столбце "Адрес сети назначения" указываются адреса всех сетей, которым данный маршрутизатор может передавать пакеты. В стеке TCP/IP принят так называемый одношаговый подход к оптимизации маршрута продвижения пакета (next-hop routing) - каждый маршрутизатор и конечный узел принимает участие в выборе только одного шага передачи пакета. Поэтому в каждой строке таблицы маршрутизации указывается не весь маршрут в виде последовательности IP-адресов маршрутизаторов, через которые должен пройти пакет, а только один IP-адрес - адрес следующего маршрутизатора, которому нужно передать пакет. Вместе с пакетом следующему маршрутизатору передается ответственность за выбор следующего шага маршрутизации. Одношаговый подход к маршрутизации означает распределенное решение задачи выбора маршрута. Это снимает ограничение на максимальное количество транзитных маршрутизаторов на пути пакета.
7
(Альтернативой одношаговому подходу является указание в пакете всей последовательности маршрутизаторов, которые пакет должен пройти на своем пути. Такой подход называется маршрутизацией от источника - Source Routing. В этом случае выбор маршрута производится конечным узлом или первым маршрутизатором на пути пакета, а все остальные маршрутизаторы только отрабатывают выбранный маршрут, осуществляя коммутацию пакетов, то есть передачу их с одного порта на другой. Алгоритм Source Routing применяется в сетях IP только для отладки, когда маршрут задается в поле Резерв (IP OPTIONS) пакета.)
В случае, если в таблице маршрутов имеется более одной строки, соответствующей одному и тому же адресу сети назначения, то при принятии решения о передаче пакета используется та строка, в которой указано наименьшее значение в поле "Расстояние до сети назначения".
При этом под расстоянием понимается любая метрика, используемая в соответствии с заданным в сетевом пакете классом сервиса. Это может быть количество транзитных маршрутизаторов в данном маршруте (количество хопов от hop - прыжок), время прохождения пакета по линиям связи, надежность линий связи, или другая величина, отражающая качество данного маршрута по отношению к конкретному классу сервиса. Если маршрутизатор поддерживает несколько классов сервиса пакетов, то таблица маршрутов составляется и применяется отдельно для каждого вида сервиса (критерия выбора маршрута).
Для отправки пакета следующему маршрутизатору требуется знание его локального адреса, но в стеке TCP/IP в таблицах маршрутизации принято использование только IP-адресов для сохранения их универсального формата, не зависящего от типа сетей, входящих в интерсеть. Для нахождения локального адреса по известному IP-адресу необходимо воспользоваться протоколом ARP.
Конечный узел, как и маршрутизатор, имеет в своем распоряжении таблицу маршрутов унифицированного формата и на основании ее данных принимает решение, какому маршрутизатору нужно передавать пакет для сети N. Решение о том, что этот пакет нужно вообще маршрутизировать, компьютер принимает в том случае, когда он видит, что адрес сети назначения пакета отличается от адреса его собственной сети (каждому компьютеру при конфигурировании администратор присваивает его IP-адрес или несколько IP-адресов, если компьютер одновременно подключен к нескольким сетям ). Когда компьютер выбрал следующий маршрутизатор, то он просматривают кэш-таблицу адресов своего протокола ARP и, может быть, находит там соответствие IP-адреса следующего маршрутизатора его MAC-адресу. Если же нет, то по локальной сети передается широковещательный ARP-запрос и локальный адрес извлекается из ARP-ответа.
После этого компьютер формирует кадр протокола, используемого на выбранном порту, например, кадр Ethernet, в который помещает МАС-адрес маршрутизатора. Маршрутизатор принимает кадр Ethernet, извлекает из него пакет IP и просматривает свою таблицу маршрутизации для нахождения следующего маршрутизатора. При этом он выполняет те же действия, что и конечный узел.
Одношаговая маршрутизация обладает еще одним преимуществом - она позволяет сократить объем таблиц маршрутизации в конечных узлах и маршрутизаторах за счет использования в качестве номера сети назначения так называемого маршрута по умолчанию - default, который обычно занимает в таблице маршрутизации последнюю строку. Если в таблице маршрутизации есть такая запись, то все пакеты с номерами сетей, которые отсутствуют в таблице маршрутизации, передаются маршрутизатору, указанному в строке default. Поэтому маршрутизаторы часто хранят в своих таблицах ограниченную информацию о сетях интерсети, пересылая пакеты для остальных сетей в порт и маршрутизатор, используемые по умолчанию. Подразумевается, что маршрутизатор, используемый по умолчанию, передаст пакет на магистральную сеть, а маршрутизаторы, подключенные к магистрали, имеют полную информацию о составе интерсети.
Особенно часто приемом маршрутизации по умолчанию пользуются конечные узлы. Хотя они также в общем случае имеют в своем распоряжении таблицу маршрутизации, ее объем обычно незначителен, так как маршрутизация для компьютера - не основное занятие. Главная роль в маршрутизации пакетов в концепции протокола IP отводится, естественно, маршрутизаторам, которые должны обладать гораздо более полными таблицами маршрутизации, чем конечные узлы. Конечный узел часто вообще работает без таблицы маршрутизации, имея только сведения об IP-адресе маршрутизатора по умолчанию. При наличии одного маршрутизатора в локальной сети этот вариант - единственно возможный для всех конечных узлов. Но даже при наличии нескольких маршрутизаторов в локальной сети , когда проблема их выбора стоит перед конечным узлом, задание маршрута по умолчанию часто используется в компьютерах для сокращения объема их маршрутной таблицы.
Другим способом разгрузки компьютера от необходимости ведения больших таблиц маршрутизации является получение от маршрутизатора сведений о рациональном маршруте для какой-нибудь конкретной сети с помощью протокола ICMP.
Кроме маршрута default, в таблице маршрутизации могут встретиться два типа специальных записей - запись о специфичном для узла маршруте и запись об адресах сетей, непосредственно подключенных к портам маршрутизатора.
Специфичный для узла маршрут содержит вместо номера сети полный IP-адрес, то есть адрес, имеющий ненулевую информацию не только в поле номера сети, но и в поле номера узла. Предполагается, что для такого конечного узла маршрут должен выбираться не так, как для всех остальных узлов сети, к которой он относится. В случае, когда в таблице есть разные записи о продвижении пакетов для всей сети N и ее отдельного узла, имеющего адрес N,D, при поступлении пакета, адресованного узлу N,D, маршрутизатор отдаст предпочтение записи для N,D.
Записи в таблице маршрутизации, относящиеся к сетям, непосредственно подключенным к маршрутизатору, в поле "Расстояние до сети назначения" содержат нули.
Еще одним отличием работы маршрутизатора и конечного узла при выборе маршрута является способ построения таблицы маршрутизации. Если маршрутизаторы обычно автоматически создают таблицы маршрутизации, обмениваясь служебной информацией, то для конечных узлов таблицы маршрутизации создаются, как правило, вручную администраторами, и хранятся в виде постоянных файлов на дисках.
Существуют различные алгоритмы построения таблиц для одношаговой маршрутизации. Их можно разделить на три класса:
алгоритмы фиксированной маршрутизации,
алгоритмы простой маршрутизации,
алгоритмы адаптивной маршрутизации.
Независимо от алгоритма, используемого для построения таблицы маршрутизации, результат их работы имеет единый формат. За счет этого в одной и той же сети различные узлы могут строить таблицы маршрутизации по своим алгоритмам, а затем обмениваться между собой недостающими данными, так как форматы этих таблиц фиксированы. Поэтому маршрутизатор, работающий по алгоритму адаптивной маршрутизации, может снабдить конечный узел, применяющий алгоритм фиксированной маршрутизации, сведениями о пути к сети, о которой конечный узел ничего не знает.
Фиксированная маршрутизация
Этот алгоритм применяется в сетях с простой топологией связей и основан на ручном составлении таблицы маршрутизации администратором сети. Алгоритм часто эффективно работает также для магистралей крупных сетей, так как сама магистраль может иметь простую структуру с очевидными наилучшими путями следования пакетов в подсети, присоединенные к магистрали.
Различают одномаршрутные таблицы, в которых для каждого адресата задан один путь, и многомаршрутные таблицы, определяющие несколько альтернативных путей для каждого адресата. При использовании многомаршрутных таблиц должно быть задано правило выбора одного из них. Чаще всего один путь является основным, а остальные - резервными.
Простая маршрутизация
Алгоритмы простой маршрутизации подразделяются на три подкласса:
Случайная маршрутизация - пакеты передаются в любом, случайном направлении, кроме исходного.
Лавинная маршрутизация - пакеты передаются во всех направлениях, кроме исходного (применяется в мостах для пакетов с неизвестным адресом доставки).
Маршрутизация по предыдущему опыту - таблицы маршрутов составляются на основании данных, содержащихся в проходящих через маршрутизатор пакетах. Именно так работают прозрачные мосты, собирая сведения об адресах узлов, входящих в сегменты сети. Такой способ маршрутизации обладает медленной адаптируемостью к изменениям топологии сети.
Адаптивная маршрутизация
Это основной вид алгоритмов маршрутизации, применяющихся маршрутизаторами в современных сетях со сложной топологией. Адаптивная маршрутизация основана на том, что маршрутизаторы периодически обмениваются специальной топологической информацией об имеющихся в интерсети сетях, а также о связях между маршрутизаторами. Обычно учитывается не только топология связей, но и их пропускная способность и состояние.
Адаптивные протоколы позволяют всем маршрутизаторам собирать информацию о топологии связей в сети, оперативно отрабатывая все изменения конфигурации связей. Эти протоколы имеют распределенный характер, который выражается в том, что в сети отсутствуют какие-либо выделенные маршрутизаторы, которые бы собирали и обобщали топологическую информацию: эта работа распределена между всеми маршрутизаторами.
Пример взаимодействия узлов с использованием протокола IP
Рассмотрим на примере интерсети, приведенной на рисунке 4.3, каким образом происходит взаимодействие компьютеров через маршрутизаторы и доставка пакетов компьютеру назначения.
Пусть в приведенном примере пользователь компьютера cit.dol.ru, находящийся в сети Ethernet с IP-адресом 194.87.23.0 (адрес класса С), хочет взаимодействовать по протоколу FTP с компьютером s1.msk.su, принадлежащем сети Ethernet с IP-адресом 142.06.0.0 (адрес класса В). Компьютер cit.dol.ru имеет IP-адрес 194.87.23.1.17, а компьютер s1.msk.su - IP-адрес 142.06.13.14.
Пользователь компьютера cit.dol.ru знает символьное имя компьютера s1.msk.su, но не знает его IP-адреса, поэтому он набирает команду
> ftp s1.msk.su
для организации ftp-сеанса.
В компьютере cit.dol.ru должны быть заданы некоторые параметры для стека TCP/IP, чтобы он мог выполнить поставленную перед ним задачу.
В число этих параметров должны входить собственный IP-адрес, IP-адрес DNS-сервера и IP-адрес маршрутизатора по умолчанию. Так как к сети Ethernet, к которой относится компьютер cit.dol.ru, подключен только один маршрутизатор, то таблица маршрутизации конечным узлам этой сети не нужна, достаточно знать IP-адрес маршрутизатора по умолчанию. В данном примере он равен 194.87.23.1.
Так как пользователь в команде ftp не задал IP-адрес узла, с которым он хочет взаимодействовать, то стек TCP/IP должен определить его самостоятельно. Он может сделать запрос к серверу DNS по имеющемуся у него IP-адресу, но обычно каждый компьютер сначала просматривает свою собственную таблицу соответствия символьных имен и IP-адресов. Такая таблица хранится чаще всего в виде текстового файла простой структуры - каждая его строка содержит запись об одном символьном имени и его IP-адресе. В ОС Unix такой файл традиционно носит имя HOSTS.
Будем считать, что компьютер cit.dol.ru имеет файл HOSTS, а в нем есть строка
142.06.13.14 s1.msk.su.
Поэтому разрешение имени выполняется локально, так что протокол IP может теперь формировать IP-пакеты с адресом назначения 142.06.13.14 для взаимодействия с компьютером s1.msk.su.
Протокол IP компьютера cit.dol.ru проверяет, нужно ли маршрутизировать пакеты для адреса 142.06.13.14. Так как адрес сети назначения равен 142.06.0.0, а адрес сети , к которой принадлежит компьютер , равен 194.87.23.0, то маршрутизация необходима.
Компьютер cit.dol.ru начинает формировать кадр Ethernet для отправки IP-пакета маршрутизатору по умолчанию с IP-адресом 194.87.23.1. Для этого ему нужен МАС-адрес порта маршрутизатора, подключенного к его сети. Этот адрес скорее всего уже находится в кэш-таблице протокола ARP компьютера , если он хотя бы раз за последнее включение обменивался данными с компьютерами других сетей . Пусть этот адрес в нашем примере был найден именно в кэш-памяти. Обозначим его МАС11, в соответствии с номером маршрутизатора и его порта.
В результате компьютер cit.dol.ru отправляет по локальной сети кадр Ethernet, имеющий следующие поля:
DA (Ethernet) ... DESTINATION IP ... ...
МАС11
142.06.13.14
Кадр принимается портом 1 маршрутизатора 1 в соответствии с протоколом Ethernet, так как МАС-узел этого порта распознает свой адрес МАС11. Протокол Ethernet извлекает из этого кадра IP-пакет и передает его программному обеспечению маршрутизатора, реализующему протокол IP. Протокол IP извлекает из пакета адрес назначения и просматривает записи своей таблицы маршрутизации. Пусть маршрутизатор 1 имеет в своей таблице маршрутизации запись
142.06.0.0 135.12.0.11 2 1,
которая говорит о том, что пакеты для сети 142.06. 0.0 нужно передавать маршрутизатору 135.12.0.11, подключенному к той же сети, что и порт 2 маршрутизатора 1.
Маршрутизатор 1 просматривает параметры порта 2 и находит, что он подключен к сети FDDI. Так как сеть FDDI имеет значение максимального транспортируемого блока MTU больше, чем сеть Ethernet, то фрагментация поля данных IP-пакета не требуется. Поэтому маршрутизатор 1 формирует кадр формата FDDI, в котором указывает MAC-адрес порта маршрутизатора 2, который он находит в своей кэш-таблице протокола ARP:
DA (FDDI) ... DESTINATION IP ... ...
МАС21
142.06.13.14
Аналогично действует маршрутизатор 2, формируя кадр Ethernet для передачи пакета маршрутизатору 3 по сети Ethernet c IP-адресом 203.21.4.0:
DA (Ethernet) ... DESTINATION IP ... ...
МАС32
142.06.13.14
Наконец, после того, как пакет поступил в маршрутизатор сети назначения - маршрутизатор 3, появляется возможность передачи этого пакета компьютеру назначения. Маршрутизатор 3 видит, что пакет нужно передать в сеть 142.06.0.0, которая непосредственно подключена к его первому порту. Поэтому он посылает ARP-запрос по сети Ethernet c IP-адресом компьютера s1.msk.su (считаем, что этой информации в его кэше нет), получает ответ, содержащий адрес MACs1, и формирует кадр Ethernet, доставляющий IP-пакет по локальной сети адресату.
DA (Ethernet) ... DESTINATION IP ... ...
МАСs1
142.06.13.14
Структуризация сетей IP с помощью масок
Часто администраторы сетей испытывают неудобства, из-за того, что количество централизовано выделенных им номеров сетей недостаточно для того, чтобы структурировать сеть надлежащим образом, например, разместить все слабо взаимодействующие компьютеры по разным сетям .
В такой ситуации возможны два пути. Первый из них связан с получением от NIC дополнительных номеров сетей. Второй способ, употребляющийся более часто, связан с использованием так называемых масок, которые позволяют разделять одну сеть на несколько сетей.
Маска - это число, двоичная запись которого содержит единицы в тех разрядах, которые должны интерпретироваться как номер сети.
Например, для стандартных классов сетей маски имеют следующие значения:
255.0.0.0
маска для сети класса А,
255.255.0.0
маска для сети класса В,
255.255.255.0
маска для сети класса С.
В масках, которые использует администратор для увеличения числа сетей, количество единиц в последовательности, определяющей границу номера сети, не обязательно должно быть кратным 8, чтобы повторять деление адреса на байты.
Пусть, например, маска имеет значение 255.255.192.0 (11111111 11111111 11000000 00000000). И пусть сеть имеет номер 129.44.0.0 (10000001 00101100 00000000 00000000), из которого видно, что она относится к классу В. После наложения маски на этот адрес число разрядов, интерпретируемых как номер сети, увеличилось с 16 до 18, то есть администратор получил возможность использовать вместо одного, централизованно заданного ему номера сети, четыре:
129.44.0.0
(10000001 00101100 00000000 00000000)
129.44.64.0
(10000001 00101100 01000000 00000000)
129.44.128.0
(10000001 00101100 10000000 00000000)
129.44.192.0
(10000001 00101100 11000000 00000000)
Например, IP-адрес 129.44.141.15 (10000001 00101100 10001101 00001111), который по стандартам IP задает номер сети 129.44.0.0 и номер узла 0.0.141.15, теперь, при использовании маски, будет интерпретироваться как пара:
129.44.128.0 - номер сети, 0.0. 13.15 - номер узла.
Таким образом, установив новое значение маски, можно заставить маршрутизатор по-другому интерпретировать IP-адрес. При этом два дополнительных последних бита номера сети часто интерпретируются как номера подсетей.
Еще один пример. Пусть некоторая сеть относится к классу В и имеет адрес 128.10.0.0 (рисунок 4.4). Этот адрес используется маршрутизатором, соединяющим сеть с остальной частью интерсети. И пусть среди всех станций сети есть станции, слабо взаимодействующие между собой. Их желательно было бы изолировать в разных сетях. Для этого сеть можно разделить на две сети, подключив их к соответствующим портам маршрутизатора, и задать для этих портов в качестве маски, например, число 255.255.255.0, то есть организовать внутри исходной сети с централизовано заданным номером две подсети класса C (можно было бы выбрать и другой размер для поля адреса подсети). Извне сеть по-прежнему будет выглядеть, как единая сеть класса В, а на местном уровне это будут две отдельные сети класса С. Приходящий общий трафик будет разделяться местным маршрутизатором между подсетями.
Необходимо заметить, что, если принимается решение об использовании механизма масок, то соответствующим образом должны быть сконфигурированы и маршрутизаторы, и компьютеры сети.
Взято с
Что такое реестр?
Что такое реестр?
Реестр - это системная база данных. Получить доступ к ней можно написав в командной строке ("ПУСК > "Выполнить") слово "RegEdit" - при этом запуститься программа для редактирования реестра. Окно этой программы поделено на две части. В левой (более узкой панели) показана древовидная структура ключей. Ключ - это раздел, отвечающий за какие-либо установки. Сами установки называются параметрами, находящимися в правой панели. Каждый параметр имеет своё имя, значение и тип. Параметры бывают строкового типа, двоичного и типа DWORD. Их очень много, но их назначение зависит от того, в каком ключе находится той или иной параметр. Ключи делятся между шестью основными разделами:
HKEY_CLASSES_ROOT - Содержит информацию об OLE, операциях перетаскивания (drag-and-drop - с англ. перетащить-и-отпустить) и ярлыках. В данном разделе можно так же указать программы, запускаемые при активизации файлов определённого типа. Данный раздел является псевдонимом для ветви HKEY_LOCAL_MACHINE\Software\Classes
HKEY_CURRENT_USER - Содержит индивидуальные установки для каждого пользователя, зарегистрированного в системе. Данный раздел является псевдонимом для ветви HKEY_USERS
HKEY_LOCAL_MACHINE - Содержит аппаратные и программные установки, необходимые для функционирования оборудования и программ. Данный раздел так же хранит конфигурацию Windows.
HKEY_USERS - Содержит установки пользователей и соответствующие конфигурационные данные, такие как цвет окна, расположение элементов на рабочем столе, обои, заставки.
HKEY_CURRENT_CONFIG - Содержит информацию о текущем аппаратном профиле. Если вы не используете аппаратные профили, данный раздел содержит установки Windows по умолчанию.
HKEY_DYN_DATA - В отличие от других разделов, которые хранят статистические данные (неизменяющиеся во время сеанса), данный раздел содержит указатели на динамические данные (постоянно изменяющиеся во время работы компьютера). Windows использует данный раздел для отслеживания профилей оборудования plug-and-play, статистики по производительности и драйверов виртуальных устройств VxD.
Все данные системного реестра заключаются в двух файлах, находящихся в директории Windows - это System.dat и User.dat.
Взято с
Что такое сокет?
Что такое сокет?
Существует мировой стандарт структуры протоколов связи - семиуровневая OSI (Open Systems Interface - интерфейс открытых систем). Hа каждом из уровней этой структуры решается свой объем задач своими методами. Сокеты находятся на так назывемом транспортном уровне - ниже находится сетевой протокол IP, выше - специализированные протоколы сеансового уровня, ориентированные на решение конкретных задач - это всем известные FTP, SMTP, etc.
Если смотреть по сути, сокет - это модель одного конца сетевого соединения, со всеми присущими ему свойствами, и, естественно - возможностью получать и передавать данные. По содержанию - это прикладной программный интерфейс, входящий в состав многих ОС. В семействе Windows - начиная с версии 3.11, и носит название WinSock. Прототипы функций WinSock API находятся в файле winsock.pas. В Delphi есть полноценная инкапсуляция клиентского и серверного сокетов, представленная компонентами TClientSocket и TServerSocket, находящимися на закладке Internet.
Сокеты не обязательно базируются на протоколе TCP/IP, они могут также базироваться на IPX/SPX, etc.
Также Вам следует ознакомиться со списком зарезервированных номеров портов.
Механизм взаимодействия сокетов таков. С одной из двух сторон запускается серверный сокет, который сразу после запуска находится в режиме прослушивания (listening), точнее - ожидания запросов от клиентов. После получения запроса от клиента устанавливается связь, и создается новый экземпляр серверного сокета.
Так как работа с сокетами, это, по сути - операции ввода/вывода, которые бывают синхронными или асинхронными, то и тип работы сокета обладает бывает синхронным или асинхронным. Компоненты TClientSock и TServerSock поддерживают оба режима работы.
Дополнение от Анатолия Подгорецкого:
Когда говорят СОКЕТ то часто не представляют, что это такое. Можно говорить об моделях, об реализациях и так далее. о есть одно простое толкование, применимое для протокола IP. Как известно для взаимодействия между машинами по протоколу IP используются адреса и порты.
Первое на текущий момент представляют из себя 32-x битный адрес, наиболее часто его представляют в символьной форме mmm.nnn.ppp.qqq (адрес разбитый на четыре октета по одному байту в октете и разделеный точками) .
Второе - это номер порта в диапазоне от нуля до 65535
Так вот эта пара и есть сокет (гнездо в в котором расположены адрес и порт).
В процессе обмена как правило используются два сокета - сокет отправителя и сокет получателя.
апример при обращении к моему серверу на HTTP порт сокет будет выглядеть так: 194.106.118.30:80, а ответ будет поступать на mmm.nnn.ppp.qqq:xxx
Взято с
Что такое сообщения Windows?
Что такое сообщения Windows?
Потребности программиста на Delphi практически полностью удовлетворяются возможностями работы с событиями, предоставляемыми VCL. Но при создании серьёзных нестандартных приложений и особенно при разработке компонентов Delphi вам, безусловно потребуется непосредственно обрабатывать сообщения Windows, после чего генерировать события, соответствующие этим сообщениям.
Что же такое сообщение? Сообщение - это извещение о некотором имевшем место событии, посылаемое системой Windows в адрес приложения. Любые действия пользователя - щелчок мышью, изменение размеров окна приложения, нажатие клавиши на клавиатуре - вынуждают Windows отправить приложению сообщение, извещающее о том, что же произошло в системе. Сообщение представляет собой определённую запись, объявленную в модуле Windows так:
type
TMsg = packed record
hwnd: HWND; // Дескриптор окна-получателя
message: UINT; // Идентификатор сообщения
WParam: WPARAM; // 32 Бита дополнительной информации
LParam: LPARAM; // Ещё 32 бита дополнительной информации
time: DWORD; // Время создания сообщения
pt: TPoint; // Положение указателя мыши в момент создания сообщения
end;
Итак, мы познакомились с тем, что представляет собой сообщение в целом, в последующих разделах будут подробно рассмотрены различные типы этих сообщений.
Взято с
Что такое System Tray ? О чем идет речь?
Что такое System Tray ? О чем идет речь?
Если Вы в операционной сиcтеме Windows пользуетесь оболочкой Explorer, то справа на TaskBar'е Вы должны были видеть " углубленную" область в которой, обычно, помещаются часы, переключатель клавиатуры, регулятор громкости и некоторые другие утилиты. Они изображаются маленькими иконками и для них существуют ToolTip'ы как для кнопок ToolBar'ов. При щелчке или двойном щелчке по такой иконке программа обычно выполняет действие по умолчанию, а при щелчке правой кнопкой показывает Pop-Up меню. Hа уровне оболочки System Tray это приложение, поддерживающее окно, которое вы видите как " углубленную" область и некоторый сервис для работы с этим окном.
Взято из FAQ:
Cколько файлов есть в определённой папке?
Cколько файлов есть в определённой папке?
Как наиболее быстрым способом узнать, сколько файлов с определенным расширением есть в определенной папке?
Например для HTM файлов:
Function GetFileCount(Dir:string):integer;
var fs:TSearchRec;
begin
Result:=0;
if FindFirst(Dir+'\*.htm',faAnyFile-faDirectory-faVolumeID, fs)=0 then
repeat
inc(Result);
until FindNext(fs)<>0;
FindClose(fs);
end;
Автор ответа: Vit
Взято с Vingrad.ru
CMYK --> RGB
CMYK --> RGB
procedure CMYKTORGB(C : byte;
M: byte;
Y : byte;
K : byte;
var R : byte;
var G : byte;
var B : byte);
begin
if (Integer(C) + Integer(K)) 255 then MinColor := 255 - K;
C := C - MinColor;
M := M - MinColor;
Y := Y - MinColor;
K := K + MinColor;
end;
COM. Агрегация и нотификация вообще и для Delphi в частности
COM. Агрегация и нотификация вообще и для Delphi в частности
Автор: Виталий Маматов
Специально для Королевства Delphi
Вступление.
К написанию данной статьи меня подтолкнула моя недавняя пробежка по вопросам относительно COM. Значительное их число сводилось к непониманию принципов организации агрегирования и нотификации. И, дабы лишний раз не утруждать себя напрасно, было решено, дать такой развёрнутый ответ с примером и сразу для всех.
Про литературу.
Самой полезной книжкой по технологии COM для меня стала неброская книжонка А.Кобирниченко "Visual Studio 6. Искусство программирования". Для примера два одинаковых понятия из тоже хорошей книжки Елмановой и Трепалина "Delphi 4 технология COM" но несколько путаной:
Apartment:
Елманова:
"Можно вызывать методы объекта только из того потока, где объект был создан. При этом одновременно можно создать несколько отличающихся объектов в разных потоках, но каждый объект обязан вызываться только из того потока, где был создан".
Кобирниченко:
"Каждый объект выполняется в своём потоке. Потоков может быть несколько, но всю синхронизацию берёт на себя сама библиотека. Объект, выполняющийся в одном потоке, ничего не знает о других потоках, поэтому может не заботится о многопоточном доступе к своим методам."
Теоретические экскурсы в данной статье, в основном, основаны на книге Кобирниченко.
Часть первая: Теория.
Агрегация.
При агрегировании внешний объект не реализует сам интерфейсы имеющиеся у внутреннего. Вместо этого он передаёт своему клиенту указатель указатель непосредственно на интерфейс внутреннего. Клиент напрямую общается с внутренним объектом. Взамен появляются требования к внутреннему объекту связанные с реализацией IUnknown внутреннего объекта. Все вызовы IUnknown внутреннего объекта должны делегироваться методам IUnknown внешнего объекта.
Нотификация.
Помимо методов реализующих входящие интерфейсы вызываемые клиентом, Объект может объявить исходящие интерфейсы, реализация которых возложена на самого клиента.
Исходящие интерфейсы являются расширением принципа уведомления, реализованного в составных документах всё уведомление в которых построено на IAdviseSink с ограниченным набором событий. Установление соединения на основе этого интерфейса требует всего одного вызова IOleObject::SetAdise.
При использовании точек соединения нужно четыре вызова: QueryInterface для получения IConnectionPointContainer, затем FindConnectionPoint для получения нужной точки соединения, затем Advise для передачи указателя на IUnknown исходящего интерфейса и, наконец, QueryInterface со стороны клиента для получения самого исходящего интерфейса. Вся эта деятельность, особенно в случае DCOM, может занять значительное время. Собственно по этому сама Microsoft рекомендует организовывать уведомление на основе собственных интерфейсов, похожих на IadviseSink, а не на основе точек соединения.
После такого введения, я думаю, вы уже готовы взять в руки инструмент Исследователя - IDE Delphi. В нашем случае ;).
Часть вторая: Махровая практика.
Агрегирование:
После тщательных поисков по Дельфийскому хелпу в данной предметной области, мною было обнаружено следующее: "TAggregatedObject is used as part of an aggregate that has a single controlling Iunknown" И приписка: "Note: For more information about aggregation, controlling objects, and interfaces, see the Inside OLE, second edition, by Kraig Brockschmidt" Ну, второе нам сейчас ни к чему, а вот с первым следует ознакомиться поближе.
Итак, вот он:
TAggregatedObject= class
private
FController: Pointer;
function GetController: IUnknown;
protected
{ IUnknown }
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
constructor Create(Controller: IUnknown);
property Controller: IUnknown read GetController;
end;
constructor TAggregatedObject.Create(Controller: IUnknown);
begin
FController := Pointer(Controller);
end;
function TAggregatedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Result := IUnknown(FController).QueryInterface(IID, Obj);
end;
и т.д. В общем ясен перец.
Теперь следующая проблема. Если мы хотим организовывать нашу библиотеку на основе TAutoObject (а нам этого очень хочется, так как по жизни мы ленивы), то, нам следует каким-то образом заставить его воспринимать наш агрегируемый объект. Способ единственный - перекрытие метода TAutoObject::QueryInterface и собственная реализация данного метода. Проблема в том, что понятие полиморфизма к интерфейсным методам неприменимо и вызываемый метод зависит только от типа ссылки на класс.
В ATL эта проблема решается применением шаблонов классов. В результате чего получается, что все методы реализованные в шаблоне _как_бы_ виртуальные. Это здорово придумано, берёшь любой метод, перекрываешь его и никаких гвоздей. Только надо учитывать, что после сборки, на этане выполнения, никакие фокусы с полиморфными вызовами у вас не пройдут.
Однако, вернёмся к нашим баранам. Просматривая, в некотором унынии, предков нашего обожаемого TAutoObject была обнаружена следующая забавная конструкция:
TComObject = class(TObject, IUnknown, ISupportErrorInfo)
..
protected
{ IUnknown }
function IUnknown.QueryInterface = ObjQueryInterface;
..
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
..
public
function ObjQueryInterface(const IID: TGUID; out Obj): HResult; virtual;
stdcall;
..
end;
Это явно не спроста, что же получается, уважаемая Borland, виртуализируем QueryInterface, сами забавляемся полученным результатом, а простым бедным программерам ни слова? Некрасиво!
Ну, думаю, с этим моментом также всё ясно, перекрываем ObjQueryInterface и дело в шляпе. Пошли дальше.
Нотификация:
Каждый школьник знает, что приём и передача нотификационных сообщений в COM производится через интерфейс IconnectionPointContainer. Дочитав MSDN до этого места, большинство программеров, тут же всё бросают и начинают реализовывать свою нотификацию на основе этого интерфейса. Но мы не так наивны, мы пойдём другим путём. На самом деле, реализовать собственную нотификацию, гораздо проще, чем это можно подумать. Работает как во внутренних, так и в локальных серверах, а заодно и в удалённых. Впрочем последнее лично не проверял. Идея: см. IAdviseSink, и мой пример по его мотивам.
Взято с
ComboBox, DBComboBox
ComboBox, DBComboBox
Cодержание раздела:
Converting 16bit resource to 32bit
Converting 16bit resource to 32bit
If you have the original source file (.rc) then you can simply recompile the .rc file to a .res file using the Borland Resource Command Line Compiler (brcc32.exe) located in the Delphi/C++ Builders bin directory. If you only have a .res file to work with, you will need to use a quality resource compiler/decompiler such as Borland's Resource Workshop. Versions of the Borland's Resource Workshop later than 4.5 can extract, compile, and decompile both 16 and 32 bit resource files from a variety of sources including .res, .exe, .dll, drv, and .cpl files. The Borland Resource Workshop version 4.5 ship with the Borland RAD Pack product line
Cписок зарезервированных слов Local SQL BDE
Cписок зарезервированных слов Local SQL BDE
ACTIVE, ADD, ALL, AFTER, ALTER, AND, ANY, AS, ASC, ASCENDING,
AT, AUTO, AUTOINC, AVG
BASE_NAME, BEFORE, BEGIN, BETWEEN, BLOB, BOOLEAN, BOTH, BY,
BYTES
CACHE, CAST, CHAR, CHARACTER, CHECK, CHECK_POINT_LENGTH,
COLLATE, COLUMN, COMMIT, COMMITTED, COMPUTED, CONDITIONAL,
CONSTRAINT, CONTAINING, COUNT, CREATE, CSTRING, CURRENT,
CURSOR
DATABASE, DATE, DAY, DEBUG, DEC, DECIMAL, DECLARE, DEFAULT,
DELETE, DESC, DESCENDING, DISTINCT, DO, DOMAIN, DOUBLE, DROP
ELSE, END, ENTRY_POINT, ESCAPE, EXCEPTION, EXECUTE, EXISTS,
EXIT, EXTERNAL, EXTRACT
FILE, FILTER, FLOAT, FOR, FOREIGN, FROM, FULL, FUNCTION
GDSCODE, GENERATOR, GEN_ID, GRANT, GROUP,
GROUP_COMMIT_WAIT_TIME
HAVING, HOUR
IF, IN, INT, INACTIVE, INDEX, INNER, INPUT_TYPE, INSERT,
INTEGER, INTO, IS, ISOLATION
JOIN
KEY
LONG, LENGTH, LOGFILE, LOWER, LEADING, LEFT, LEVEL, LIKE,
LOG_BUFFER_SIZE
MANUAL, MAX, MAXIMUM_SEGMENT, MERGE, MESSAGE, MIN, MINUTE,
MODULE_NAME, MONEY, MONTH
NAMES, NATIONAL, NATURAL, NCHAR, NO, NOT, NULL,
NUM_LOG_BUFFERS, NUMERIC
OF, ON, ONLY, OPTION, OR, ORDER, OUTER, OUTPUT_TYPE, OVERFLOW
PAGE_SIZE, PAGE, PAGES, PARAMETER, PASSWORD, PLAN, POSITION,
POST_EVENT, PRECISION, PROCEDURE, PROTECTED, PRIMARY,
PRIVILEGES
RAW_PARTITIONS, RDB$DB_KEY, READ, REAL, RECORD_VERSION,
REFERENCES, RESERV, RESERVING, RETAIN, RETURNING_VALUES,
RETURNS, REVOKE, RIGHT, ROLLBACK
SECOND, SEGMENT, SELECT, SET, SHARED, SHADOW, SCHEMA,
SINGULAR, SIZE, SMALLINT, SNAPSHOT, SOME, SORT, SQLCODE,
STABILITY, STARTING, STARTS, STATISTICS, SUB_TYPE, SUBSTRING,
SUM, SUSPEND
TABLE, THEN, TIME, TIMESTAMP, TIMEZONE_HOUR, TIMEZONE_MINUTE,
TO, TRAILING, TRANSACTION, TRIGGER, TRIM
UNCOMMITTED, UNION, UNIQUE, UPDATE, UPPER, USER
VALUE, VALUES, VARCHAR, VARIABLE, VARYING, VIEW
WAIT, WHEN, WHERE, WHILE, WITH, WORK, WRITE
YEAR
, -, *, /, <>, <, >, ,(comma), =, <=, >=, ~=, !=, ^=, (, )
Взято с сайта
Creating resource files
Creating resource files
You can use the Borland Command Line Resource Compiler (BRCC.EXE and BRCC32.EXE) that ships with Delphi and C++ Builder, or a WYSIWYG resource editor such as Borland's Resource Workshop 5.0 that ships with the Borland RAD Pack. The Resource Workshop 5.0 can compile and decompile both 16 and 32 bit resources from .rc, .res, .exe, .dll, .drv, .vbx, .cpl, .ico,. bmp, .rle, .dlg, fnt, and cur files.
Crystal Reports 8.0 через API
Crystal Reports 8.0 через API
Автор: Андрей Зубарев
Специально для Королевства Delphi
Вступление
Crystal Reports (далее как CR) на сегодняшний день является лидирующим пакетом для разработки отчетности в крупных компаниях. Для доступа к отчетам компания Seagate предоставляет несколько вариантов:
Элемент управления Crystal ActiveX
Компонент Report Designer Component
Компоненты VCL сторонних разработчиков.
Automation Server
Вызовы API реализуются через Report Engine API (далее RE).
По моему мнению, лучшим является доступ посредством API функций, т.к.:
вы полностью контролируете все, что происходит.
узнаете, как все это работает.
не зависите от фирмы разработчика компонент и их версий.
не платите денег (хотя этот момент расплывчат J).
В 90% случаев необходимо только вывести отчет и передать входящие параметры, т.е. вы получаете "тонкое" приложения на основе своих же наработок, что согласитесь, греет душу программиста. Предполагается, что читатель знаком с работой в Crystal Reports и понимает концепцию разработки отчетов в данной среде.
Примечание Vit: позволю себе внести поправку, фирма Seagate имеет свой собственный VCL компонент для работы со всеми версиями Crystal Report начиная с 4й и заканчивая 9й. К сожалению разработка VCL компонента обычно задерживается на пол-года, а иногда и дольше со времени выхода очередного релиза. Мне доводилось не однократно самому переделывать компонент от старой версии для более новой и обычно это не очень сложная задача. Компонент можно взять с FTP Seagate.
Необходимые файлы
Библиотека [crpe32.dll] содержит интерфейс вызовов API функций.
Модуль [uCrystalApi.pas] с описаниями API функций. Он был подправлен мной, так как было несколько синтаксических ошибок.
Для работы примера необходим источник данных, в качестве которого используется демонстрационная БД MS Access 2000 [source_db.mdb]. В качестве драйвера связи используется OLE DB для MS Jet 4.0. БД должна находиться в той же папке, где и пример отчета.
Если вы хотите распространять ваше приложение с отчетами, тогда ознакомьтесь с содержимым файла [crpe32.dep], который содержит список необходимых файлов для работы RE.
Пример реализован на Delphi 6.0.
Программируем
Первым надо "запустить машину" CR, посредством вызова функции PEOpenEngine для инициализации механизма отчетов. Надо заметить, что вызов данной функции справедлив только для одного потока.
Теперь можно и начать подготовку отчета для вывода. Вызов PEOpenPrintJob дает нам дескриптор задачи (отчета), который необходимо передавать в другие функции.
// Синтаксис функции
PEOpenPrintJob(PathToReport: PChar): SmallInt;
{
где,
PathToReport - путь к файлу отчета.
Результат функции - дескриптор полученной задачи.
Пример:
FHandleJob := PEOpenPrintJob(PChar(edtPathReport.Text));
}
Получив дескриптор, мы можем, манипулировать отчетом как нам будет угодно. Получать информацию о параметрах, об источнике данных, управлять разделами отчета и формулами.
Далее необходимо сказать системе, куда выводить отчет: в окно предварительного просмотра (…ToWindow) или на принтер (…ToPrinter).
// Синтаксис функций:
PEOutputToWindow(printJob : Smallint; title: PChar;
left: Integer; top: Integer;
width: Integer; height: Integer;
style: DWord;
parentWindow : HWnd): Bool;
PEOutputToPrinter(printJob: Word;
nCopies: Integer)): Bool;
{
где,
printJob - дескриптор задачи
title - заголовок окна
left, top, width, height - координаты окна
style - стиль окна (типа WS_VSCROLL, WS_VISIBLE и т.д.)
parentWindow - дескриптор окна в котором будет окно отчета.
nCopies - количество копий.
Пример:
Result:= PEOutputToWindow(FHandleJob,
PChar(TForm(Self).Caption),
0, 0, 0, 0, 0, FWindow);
}
Подготовив механизм вывода отправляем отчет для вывода функцией PEStartPrintJob.
// Синтаксис функции:
function PEStartPrintJob(printJob: Word;
waitUntilDone: Bool): Bool;
{
где,
printJob - дескриптор задачи.
WaitUntilDone - зарезервирован. Всегда должен быть True.
Пример:
PEStartPrintJob(FHandleJob, True);
}
После отправки отчета, если не надо производить с ним операций, закрываем задание функцией PEClosePrintJob.
// Синтаксис функции:
function PEClosePrintJob (printJob: Word): Bool;
{
где,
printJob - дескриптор задачи.
Пример:
PEClosePrintJob(FHandleJob);
}
Между вызовами функций PEOpenPrintJob и PEClosePrintJob может стоять сколько угодно вызовов функций PEOutputTo…, PEStartPrintJob.
В итоге получается схема вызовов:
PEOpenEngine
|
PEOpenPrintJob
|
PEOutputToWindow
|
PEStartPrintJob
|
PEClosePrintJob
|
PECloseEngine
Пример просмотра отчета
Ниже приведен код процедуры для просмотра отчета из примера
procedureTFrmMain.btnReportPreviewClick(Sender: TObject);
var
// Дескриптор окна в котором производится просмотр отчета
FWindow: THandle;
// Информация об источнике данных.
// См. раздел "Получение параметров и свойств источника"
lt: PELogOnInfo;
begin
// В зависимости от флага устанавливаем дескриптор окна.
// При нуле, отчет будет показан в независимом внешнем окне.
if chkWindow.Checked then
FWindow := 0
else
FWindow := pnlPreview.Handle;
// Открываем отчет и получаем дескриптор задачи.
FHandleJob := PEOpenPrintJob(PChar(edtPathReport.Text));
// Получение параметров источника данных отчета.
FillChar(lt, SizeOf(PELogOnInfo), 0);
lt.StructSize := SizeOf(PELogOnInfo);
PEGetNthTableLogOnInfo(FHandleJob, 0, lt);
// Устанавливаем новые параметры источника данных отчета.
StrPCopy(@lt.ServerName, ExtractFilePath(edtPathReport.Text) +
'source_db.mdb');
PESetNthTableLogOnInfo(FHandleJob, 0, lt, False);
// Настраиваем окно вывода.
PEOutputToWindow(FHandleJob, PChar(TForm(Self).Caption), 0, 0, 0, 0, 0,
FWindow);
// Выводим отчет.
PEStartPrintJob(FHandleJob, True);
// Закрываем дескриптор задания.
PEClosePrintJob(FHandleJob);
end;
Получение и установка свойств источника
Теперь когда мы научились выводить отчет, расширим наши познания в области манипуляций отчетом, такими как получение параметров отчета и свойств источника данных.
Свойства источника данных можно получить или установить через функции PEGetNthTableLogOnInfo и PESetNthTableLogOnInfo. Здесь надо отметить довольно тонкий момент, связанный с обработкой данных в CR. Источником данных может выступать любая СУБД как файловая, так и серверная, текстовый файл и т.п. В свою очередь к примеру из серверной СУБД данные можно получить через хранимую процедуру (stored procedure), представление (view), таблицу (table) или через набор таблиц которые обрабатываются уже внутри отчета. Поэтому используются различные API функции зависящие от возможностей источника.
Обратите внимание на название в именах функций - сокращение Nth обозначает, что функция вызывается для определенной таблицы.
Получение свойств через функцию довольно просто. Описываем структуру данных, передаем дескриптор задачи и порядковый номер таблицы. После вызова функции получаем заполненную структуру параметров.
// Синтаксис функции:
function PEGetNthTableLogOnInfo
(printJob: Word;
tableN: Integer;
var logOnInfo: PELogOnInfo): Bool;
{
где,
printJob - дескриптор задачи.
tableN - номер таблицы.
location - струкура со свойствами источника.
Пример:
PEGetNthTableLogOnInfo(FHandleJob, 0, lt);
}
Структура PELogOnInfo содержит свойства источника. Перед ее передачей в функцию обязательно заполните поле StructSize. Например:
// Чистим структуру.
FillChar(lt, SizeOf(PELogOnInfo), 0);
// Заполняем поле размера.
lt.StructSize := SizeOf(PELogOnInfo);
// Вызываем функцию для таблицы с порядковым номером 0 (ноль)
PEGetNthTableLogOnInfo(FHandleJob, 0, lt);
Описание структуры:
type
PELogonServerType = array[0..PE_SERVERNAME_LEN - 1] of Сhar;
PELogonDBType = array[0..PE_DATABASENAME_LEN - 1] of Сhar;
PELogonUserType = array[0..PE_USERID_LEN - 1] of Сhar;
PELogonPassType = array[0..PE_PASSWORD_LEN - 1] of Сhar;
PELogOnInfo = record
StructSize: Word;
ServerName: PELogonServerType;
DatabaseName: PELogonDbType;
UserId: PELogonUserType;
Password: PELogonPassType;
end;
{
где,
StructSize - размер структуры.Заполняется обязательно.
ServerName - имя сервера или путь к файлу БД.
DatabaseName - имя БД.
UserId - пользователь.
Password - пароль пользователя.
}
Функция установки параметров PESetNthTableLogOnInfo аналогично предыдущей (в смысле параметров, а действует наоборот - устанавливает новые свойства источника). У данной функции есть один дополнительный логический параметр propagateAcrossTables, который указывает как обработать информацию из структуры PELogOnInfo. Если значение параметра TRUE, тогда свойства из структуры применяются для всех таблиц в отчете, иначе только для таблицы с с номером tableN. Например:
// Скопировать в поле ServerName путь к БД отчета.
StrPCopy(@lt.ServerName, ExtractFilePath(edtPathReport.Text) + 'source_db.mdb');
// Установить параметры для таблицы 0 и только для нее.
PESetNthTableLogOnInfo(FHandleJob, 0, lt, False);
Получение параметров отчета
Теперь о том как получить параметры отчета с помощью которых производится управление.
Используя PEGetNParameterFields вы получаете общее количество параметров в отчете. Передавая в функцию PEGetNthParameterField порядковый номер параметра получаем структуру с данными об имени, размере, значениях и т.п. Функция PEConvertPFInfoToVInfo позволяет получить значение параметра.
Функция PEGetNParameterFields имеет только один параметр - дескриптор задачи, в результате возвращается количество параметров. В примере показано как работать с параметрами:
var
ParameterInfo: PEParameterFieldInfo;
ValueInfo: PEValueInfo;
...
// Получить количество параметров.
CountParams := PEGetNParameterFields(FHandleJob);
if CountParams <> -1 then
begin
for i := 0 to CountParams - 1 do
begin
// Запросить информацию о параметре i.
PEGetNthParameterField(FHandleJob, i, ParameterInfo);
ValueInfo.ValueType := ParameterInfo.valueType;
// Получить значение параметра.
PEConvertPFInfoToVInfo(@ParameterInfo.DefaultValue,
ValueInfo.ValueType,
ValueInfo);
...
end;
end;
Описания структур довольно большие, поэтому я опишу только те поля которые используются в примере.
ParameterInfo.Name // - имя параметра.
ParameterInfo.ValueType // - тип данных параметра.
ParameterInfo.DefaultValue // - значение по умолчанию.
Структура ValueInfo содержит в одном из своих полей значение параметра. Вы можете посмотреть в примере функцию FormatCrystalValue, чтобы разобраться с полями структуры.
Заключение
Дополнительные сведения о программировании с использованием API вы может посмотреть в справочных файлах, которые идут с CR (PROGRAM FILES\SEAGATSOFTWARE\CRYSTAL REPORTS\DEVELOPER FILES\HELP\), файлы DEVELOPR.HLP и RUNTIME.HLP. Если их у вас нет, то скачайте с FTP сервера Seagate.
В будущем я надеюсь развить тему CR более углубленно, но это зависит от интереса читателей и наличия времени :-).
Взято с
CSHelp
.topic 10000
Delphi supports a central property to hold the name of the help file: The property Application.helpfile.
You can either set the property manually at run time or you can specify it directly in the Delphi IDE under "Project Options":
Resulting in our first problem
When you click Browse in the dialog shown above, Delphi not only saves the file name but the full path as well.
But when your application is installed on another computer, the path may differ.
It would be better to specify only the file name without a path or use a relative path name like ".\help\myhelpfile.hlp"
If you do so, you have to place your help file in the same directory as your application is. This is generally a good idea! If your application uses more help files than only one, you may want to bundle them in a separate sub-directory, as the relative path name above illustrates.
Resulting in a secondary problem
If you do not specify a fully qualified path name for your help file, the help file is assumed to be in the current directory or in a directory relative to the current. When your application starts, the current directory is usually the application directory, of course. At the beginning, this condition is true. But the current directory may change while your application is running. What happens then? Correct! The help file is not found.
Making it always work
Fortunately, you can change the Application.helpfile property at run time as well.
The best place to do this, is the OnCreate event of your main form:
Add the following code to this event (myhelpfile.hlp has to be replaced with your real help file name):
procedure TMainForm.FormCreate(Sender: TObject);
begin
Application.helpfile :=
extractfilepath(application.exename) + 'myhelpfile.hlp';
end;
This defines the help file including a fully qualified path (assumed that your help file is in the same directory as your application) and works in any case, whether the current directory has changed or not.
.topic 10100
Well, your Delphi application now knows that is has a help file. Fine. But how can you display the help file?
Delphi provides you with a method called
Application.Helpcommand(command, data).
This method encapsulates all help file calls. You can call the table of contents, display a specific topic, search for a keyword and much more.
Example:
procedure TFrmHelpman.mHelpContentsClick(Sender: TObject);
begin
Application.HelpCommand(HELP_FINDER, 0);
end;
The Command parameter must match one of the predefined Windows API constants and Data must be set accordingly.
The following topic explains the available parameters. The next chapter discusses almost the same, but from a more practical point of view. It tells you the how to does.
See also
Parameters of Application.HelpCommand
.topic 10200
The online help of Delphi describes the method Application.HelpCommand in a short sentence:
"Provides quick access to any of the Help commands in the WinHelp API.
function HelpCommand(Command: Word; Data: Longint): Boolean;"
This is not much, isn't it? Well, let's examine the WinHelp API. The Winhelp API defines the following constants for Command. Depending on Command, you must support an appropriate Data parameter as well. Don't worry, the following description illustrates the usage with a code example for each.
The list of available parameters:
HELP_COMMAND
Don't mistake this constant with the general "HelpCommand" function of Delphi. This constant has an underscore between help and command. It is used to execute a macro. The Data parameter is a pointer to a string that specifies the macro. A macro must also be used to display a specific topic by topic ID.
Code example to display a specific topic
Code example for another macro
HELP_CONTENTS
Displays the so-called "default topic" or the very first topic of your help file, if the "default topic" is not explicitly defined. It does not display the table of contents of a Winhelp file as its name suggests. It comes from 16 bit times when Windows help files did not have an extra table of contents but a topic instead, that displayed an overview and links to all other topics. This old "contents" topic is still alive. If the table of contents of a help file is missing, this topic is displayed when you open the help file.
Code example how to display the default topic
HELP_CONTEXT
Displays a topic by using its help context number. This is exactly what Delphi does automatically when it displays context sensitive help. The Data parameter is an integer value that represents the help context number.
Code example for context sensitive help
HELP_CONTEXTPOPUP
Displays a topic by using its help context number. This is exactly what Delphi does automatically when it displays context sensitive help. The Data parameter is an integer value that represents the help context number.
Code example
HELP_FINDER
This command actually displays the table of contents of a Windows 95 help file. The Data parameter is zero.
Code example
HELP_FORCEFILE
Ensures that WinHelp is displaying the correct Help file. If the incorrect Help file is being displayed, WinHelp opens the correct one; otherwise, there is no action.
Code example
HELP_HELPONHELP
Displays help on how to use a help file. This command forces Winhelp to open the appropriate topics of its own help file, Winhelp.hlp. The Data parameter is zero.
Code example
HELP_INDEX
This command is obsolete. Use HELP_FINDER instead.
HELP_KEY
This command searches for a single keyword:
· If the given keyword does not match any keyword of the help file, the keyword index is displayed.
· If there is only one topic, that specifies the keyword, the topic is displayed.
· If more topics than only one specify this keyword, a list of matching topics is displayed.
The Data parameter is a pointer to a string that contains the keyword to look for.
Code example for HELP_KEY
HELP_MULTIKEY
Displays the topic specified by a keyword in an alternative keyword table. Winhelp knows two types of keywords.
· K-keywords are normal keywords defined by K-footnotes. These keywords are displayed in the keyword index on the Index tab. You can also create links to other topics by using the KLink macro and K-keywords.
· A-keywords are hidden keywords defined by A-footnotes. These keywords are not displayed in the keyword index but used for context sensitive help. When you press F1 in the Delphi IDE, Delphi searches for an A-keyword matching the text the cursor is in or the selected property name in the Object Inspector. You can also create links to other topics by using the ALink macro and A-keywords.
If this keyword functionality does not satisfy you, you can define other keywords in your help file by additional footnotes (e.g. B-footnotes like Delphi did in version 2, C-footnotes and so on). The HELP_MULTIKEY command lets you access these keywords as the HELP_KEY command lets you access the normal K-keywords.
HELP_PARTIALKEY
This command works the same way as HELP_KEY.
Code example
HELP_QUIT
Closes a help file. The Data parameter is zero.
Code example
HELP_SETCONTENTS
This command is obsolete.
HELP_SETINDEX
This command is obsolete.
HELP_SETWINPOS
Sets the position and size of a help window. The Data parameter is a pointer to a THelpWinInfo structure.
Code example
.topic 1100
The following example displays the topic "howtoregister" of your help file. You may want to use it in an about dialog when your application is a demo version only. The macro "JI()" is short for "JumpID". For a complete list of available macros please refer to the help of the Microsoft help workshop (hcw.hlp).
var
command: array[0..255] of Char;
begin
command := 'JI(howtoregister)';
application.helpcommand(HELP_COMMAND, Longint(@command));
end;
You may find it easier to use the Delphi method Application.HelpJump instead.
See also:
More information about Winhelp macros
.topic 1010
Example, how to display the about box of the current help file. It uses the Winhelp macro "About()" which has no parameters.
procedure TForm1.ShowHelpAbout;
var
command: array[0..255] of Char;
begin
command := 'About()';
application.helpcommand(HELP_COMMAND, Longint(@command));
end;
If you are curious how this about box looks, click here.
See also:
More information about Winhelp macros
.topic 1020
This example displays the default topic. The data parameter is simply zero in this case:
begin
application.helpcommand(HELP_CONTENTS, 0);
end;
.topic 1030
This example displays the "table of contents" of a Windows 95 help file:
procedure TFrmHelpman.mHelpContentsClick(Sender: TObject);
begin
Application.HelpCommand(HELP_FINDER, 0);
end;
Remember: a Windows 95 (98/NT4/2000) help file consists of two files! If the table of contents is missing, Winhelp just displays the so-called default topic instead.
.topic 1040
Display context sensitive help from a HelpContext number. The topic is displayed in the main window of your help file. To display the context sensitive help as a popup topic, use HELP_CONTEXTPOPUP.
procedure TForm1.WhatsthisClick(Sender: TObject);
var
ihc: longint;
begin
if (Sender is TWincontrol) or (Sender = TMenuitem) then
begin
ihc := TWincontrol(Sender).HelpContext;
application.helpcommand(HELP_CONTEXT, ihc);
end
else showmessage('No context sensitive help available');
end;
You may find it easier to use the Delphi method Application.HelpContext instead.
By the way, this is the HelpContext property in the Delphi IDE:
See also
Command parameter HELP_CONTEXTPOPUP
About context sensitive help
.topic 1050
Display context sensitive help from a HelpContext number in a popup window.
procedure TForm1.WhatsthisClick(Sender: TObject);
var
ihc: longint;
begin
if (Sender is TWincontrol) or (Sender = TMenuitem) then
begin
ihc := TWincontrol(Sender).HelpContext;
application.helpcommand(HELP_CONTEXTPOPUP, ihc);
end
else showmessage('No context sensitive help available');
end;
See also
Command parameter HELP_CONTEXTPOPUP
About context sensitive help
.topic 1060
This example closes the help file if it is open. The data parameter is zero in this case:
begin
application.helpcommand(HELP_QUIT, 0);
end;
.topic 1070
This example displays the "help on help" topics from Winhelp.hlp.
procedure TForm1.mHelpHowtousehelp(Sender: TObject);
begin
Application.HelpCommand(HELP_HELPONHELP, 0);
end;
.topic 1080
This example searches for a single keyword.
procedure TForm1.Button1Click(Sender: TObject);
var
keyword: string;
Command: array[0..255] of Char;
begin
keyword := Edit1.text;
StrLcopy(Command, pchar(keyword), SizeOf(Command) - 1);
Application.helpcommand(HELP_KEY, Longint(@Command));
end;
.topic 1090
1. A non-working example?
This example should set the position and size of a help window. The following code should be correct. However, I never got it to work (if you do, please tell me how):
procedure TForm1.Button1Click(Sender: TObject);
var
hwi: THelpWinInfo;
begin
with hwi do
begin
wStructSize := SizeOf(hwi);
x := 0;
y := 0;
dx := 512;
dy := 512;
wMax := SW_SHOWNORMAL;
end;
Application.HelpCommand(HELP_SETWINPOS, LongInt(@HWi));
Application.HelpCommand(HELP_CONTENTS, 0);
end;
2. A working example:
Fortunately, there is also a help macro available that we can use to set the size and position of a help window. The following code is correct and works (in all cases, I hope):
procedure TForm1.Button1Click(Sender: TObject);
var
command: array[0..255] of Char;
begin
command := 'PW(0, 0, 512, 512, 1, "main")';
application.helpcommand(HELP_COMMAND, Longint(@command));
end;
What it does
The code example above uses the HELP_COMMAND constant to execute a macro. The macro we use here is "PositionWindow" or - short - "PW". It defines the upper left corner (x1, y1) as (0,0) and the lower right corner (x2, y2) as (512,512).
The following parameter is the integer constant for "SW_SHOWNORMAL". Winhelp does not recognize the string "sw_shownormal" in the macro string.
The last parameter is the name of the window.
One word about the coordinates of a help window
Te position and size of a help window always relates to a virtual screen size of 1024 x 1024 pixel, regardless of the screen resolution. If you set the size to (0, 0, 1024, 512), the help window would cover exactly the upper half of the screen.
CUR ---> BMP
CUR ---> BMP
procedureTForm1.Button1Click(Sender: TObject);
var
hCursor: LongInt;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
Bitmap.Width := 32;
Bitmap.Height := 32;
hCursor := LoadCursorFromFile('test.cur');
DrawIcon(Bitmap.Canvas.Handle, 0, 0, hCursor);
Bitmap.SaveToFile('test.bmp');
Bitmap.Free;
end;
Взято с
Delphi Knowledge BaseCуществует ли диск в системе?
Cуществует ли диск в системе?
function DriveExists (Drive: Byte) : boolean;
begin
Result := Boolean (GetLogicalDrives and (1 shl Drive));
end;
procedure TForm1.Button1Click(Sender : TObject);
var Drive : byte;
begin
for Drive := 0 to 25 do
If DriveExists (Drive) then
begin
ListBox1.Items.Add (Chr(Drive+$41));
end;
end;
Автор Serious
Взято с Vingrad.ru
Цветные ячейки в StringGrid / DBGrid?
Цветные ячейки в StringGrid / DBGrid?
Автор: Alex Schlecht
StringGrids / DBGrids с цветными ячейками смотрятся очень красиво, и Вы можете информировать пользователя о важных данных внутри Grid.
Совместимость: все версии Delphi
К сожалению, невозможно применить один и тот же метод к StringGrids и к DBGrids. Итак сперва рассмотрим как это сделать в StringGrid:
1. StringGrid
=============
Для раскрашивания будем использовать событие "OnDrawCell". Следующий код показывает, как сделать в Grid красный бэкраунд. Бэкграунд второй колонки будет зелёным.
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
Const //сдесь определяем Ваш цвет. Так же можно использовать
//цвета по умолчанию.
clPaleGreen = TColor($CCFFCC);
clPaleRed = TColor($CCCCFF);
begin
//Если ячейка получает фокус, то нам надо закрасить её другими цветами
if (gdFocused in State) then begin
StringGrid1.Canvas.Brush.Color := clBlack;
StringGrid1.Canvas.Font.Color := clWhite;
end
else //Если же ячейка теряет фокус, то закрашиваем её красным и зелёным
if ACol = 2 //Вторая колонка будет зелёной , другие - ячейки красными
then StringGrid1.Canvas.Brush.color := clPaleGreen
else StringGrid1.canvas.brush.Color := clPaleRed;
//Теперь закрасим ячейки, но только, если ячейка не Title- Row/Column
//Естевственно это завит от того, есть у Вас title-Row/Columns или нет.
If (ACol > 0) and (ARow>0) then
begin
//Закрашиваем бэкграунд
StringGrid1.canvas.fillRect(Rect);
//Закрашиваем текст (Text). Также здесь можно добавить выравнивание и т.д..
StringGrid1.canvas.TextOut(Rect.Left,Rect.Top,StringGrid1.Cells[ACol,ARow]);
end;
end;
Если Вы захотите чтобы цвет ячеек менялся в зависимости от значения в них, то можно заменить 3 линии (if Acol = 2 ......) на что-нибуть вроде этого
if StringGrid1.Cells[ACol, ARow] = 'highlight it' then
StringGrid1.Canvas.Brush.color := clPalered
else
StringGrid1.canvas.brush.Color := clwhite;
Ну а теперь давайте раскрасим DBGrids:
2. DBGrid
=========
С DBGrids это делается намного проще. Здесь мы будем использовать событие "OnDrawColumnCell". Следующий пример разукрашивает ячейки колонки "Status" когда значение НЕ равно "a".
Если Вы хотите закрасить целую линию, то достаточно удалить условие "If..." (смотрите ниже)
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn;
State: TGridDrawState);
const
clPaleGreen = TColor($CCFFCC);
clPaleRed = TColor($CCCCFF);
begin
if Column.FieldName = 'Status' then //Удалите эту линию, если хотете закрасить целую линию
if Column.Field.Dataset.FieldbyName('Status').AsString <> 'a' then
if (gdFocused in State) {//имеет ли ячейка фокус? } then
dbgrid1.canvas.brush.color := clBlack //имеет фокус
else
dbgrid1.canvas.brush.color := clPaleGreen; //не имеет фокуса
//Теперь давайте закрасим ячейку используя стандартный метод:
dbgrid1.DefaultDrawColumnCell(rect, DataCol, Column, State)
end;
Вот и всё. Не правда ли красиво ? :)
Взято с Исходников.ru
Дает ли Delphi возможность корректно прервать выполнение SQL-запроса (BDE)?
Дает ли Delphi возможность корректно прервать выполнение SQL-запроса (BDE)?
Дает ли Delphi возможность корректно прервать выполнение SQL-запроса к серверу Oracle с помощью BDE? Например, чтобы при использовании с SQL Plus после отправки SQL-запроса на выполнение на экране появлялось окно с кнопкой Cancel, которое давало бы возможность в любой момент прервать выполнение этого запроса?
Насколько мне известно, для этой цели лучше всего использовать функции Oracle Call Interface (низкоуровневый API Oracle). В комплекте поставки Oracle есть соответствующие примеры для C, и переписать их на Pascal несложно.
Некоторые драйверы SQL Link позволяют прекратить выполнение запроса, если время его выполнения превышает заранее заданное значение (параметр MAX QUERY TIME соответствующего драйвера). Однако драйвер ORACLE, к сожалению, в их число не входит.
Наталия Елманова
Взято с Исходников.ru
Дайте теоретическое объяснение типу IDispatch
Дайте теоретическое объяснение типу IDispatch
Идентификатор интерфейса тип IDispatch, используемый для связи с объектом. Для создания объектов COM, не использующих интерфейс IDispatch, надо использовать функцию CreateComObject.
Руксскими словами: varDispatch $0009 ссылка на автоматический объект (указатель на интерфейс IDispatch)
Автор ответа: Snick_Y2K
Взято с Vingrad.ru
"Тип IDispatch" - не звучит. Ты бы сказал, в каком контексте.
Вообще, IDispatch - это интерфейс. Если ты заглянешь в System.pas, ты найдешь его делфийское описание:
IDispatch=interface
.....
end;
Это интерфейс используется для обеспечения "позднего связывания" в COM, то есть вызовов методов(и использования property) когда на момент компиляции их имена не известны. Например:
var
v:variant;
begin
v:=CreateOleObject("Excel.Appication");
v.Quit;
end;
Как тут вызывается метод Quit? Ведь компилятор совершенно ничего не знает об этом методе, ровно как и о том, что содержится в переменно v. На самом деле, одна эта строчка транслируется компилятором в набор примерно таких вызовов:
var
v:variant;
begin
v:=CreateOleObject("Excel.Appication");
if TVarData(v).vtType=vtIDispatch then
begin
pseudo_compiler_generated_IDispatch:IDispatch=TVarData(v).varIDispatch //указатель на интерфейс IDispatch
cpl_gen_DispID:integer;
pseudo_compiler_generated_IDispatch.GetIDsOfNames('Quit',1,cpl_gen_DispID); //получаем числовой индефикатор имени "Quit"
pseudo_compiler_generated_IDispatch.Invoke(cpl_gen_DispID,....); //вызывает метод по индификатору.
end;
end;
Если использоват IDispatch вместо variant, то все это можно написать самому:
var
Disp:IDispatch;
DispID:integer;
begin
Disp:=CreateOleObject("Excel.Appication");
Disp.GetIDsOfNames('Quit',1,DispID); //получаем числовой индефикатор имени "Quit"
Disp.Invoke(DispID,....); //вызывает метод по индификатору.
end;
Автор ответа: Fantasist
Взято с Vingrad.ru
Data segment too large error
Data segment too large error
When I add a large number of typed constants to my application,
my data segment grows too large. How can I get around this?
Typed constants are added to the Program's data segment. Untyped
constants are added to the code segment of the application only if
they are used. Unless you are going to reassign the value of a typed
constant, you should use an untyped constant instead. Typed constants
should be replaced with initialized variables to be compatible with
future versions of the compiler.
Example:
{This adds the constant to the data segment}
const SomeTypedString : string = 'Test';
const SomeTypedInt : integer = 5;
{This adds the constant to the code segment if used}
const SomeUnTypedString = 'Test';
const SomeUnTypedInt = 5;
Database index out of date error
Database index out of date error
This is a BDE/Paradox error message. For newbies, BDE error messages are daunting, cryptic messages. Actually, even for seasoned veterans, they can sometimes be real "stumpers." Unfortunately, there's no real good reference available that I know of, so all I can offer with respect to this error message is my experience.
The "Index out of date" message can mean a couple of things:
1.1.One of the more common causes of this error is one in which you have a couple of copies of a table existing on your network or machine. For instance, when I develop applications, I have my application tables residing in my development system, then have copies of them on my network. When I need to update my tables, I usually do the updates in my development system, then copy them over to my deployment system on the network. I've run into this exact error when I've copied only the table (.DB) file and not its accompanying index file(s) (.PX, .X01, .Y01, etc) as well. You see, when you update a table by changing it in any way, its index files are also resynched to reflect the changes. So if you copy just the table to a new place on your system and don't include its family members, you'll index files that aren't in synch with your table. Okay that's one cause.
2.2. The next cause could be just this: One of your indexes is corrupt. This could be due to sector errors on your hard disk, or the rare, but possible, direct corruption of an index. This usually happens if your program abended while performing an update to a table with an index of some sort. In that case, the index doesn't get updated.
But in any case, the only way I know of to correct the problem is to do the following:
1. Open up your table in Database Desktop.
2. Restructure it.
3. Define/Rebuild all your indexes.
4. Save the file.
Взято с
Delphi Knowledge BaseАвтор: Tom Jensen
Некоторое время назад у меня также была масса ошибок типа 'index out of date' и даже искажение данных. После продолжительного исследования я выяснил причину, она оказалось в различных установках Paradox Language в BDE (v1 и V3) на странице Driver и System в утилите конфигурирования BDE. Я не обратил внимание на установки на странице System одной из рабочих станций, и получил искажение данных.
Взято из
DBExpress
DBExpress
Cодержание раздела:
См. также статьи в других разделах:
DBGrid
DBGrid
Cодержание раздела:
См. также статьи в других разделах:
DBGrid компонент c разными цветами удалённые, обновлённые и добавленные записи
DBGrid компонент c разными цветами удалённые, обновлённые и добавленные записи
unit atcDBGrid;
(*
(c) Aveen Tech
2001 - 2002
FileName: atcDBGrid.pas
Version Date Author Comment
1.0 13/06/2000 Majid Vafai Jahan Create.
OVERVIEW
- This grid is inherited from DBGrid and add some required functionality to it.
Functionality:
- Record type are all records that may be modified, unmodified, inserted, deleted.
- Coloring according to Record type.
- show selected Record Type.
*)
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Grids, DBGrids, dbTables, db;
const
AlignFlags : array [TAlignment] of Integer =
( DT_LEFT or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX,
DT_RIGHT or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX,
DT_CENTER or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX );
RTL: array [Boolean] of Integer = (0, DT_RTLREADING);
type
TCachedShow = (csModify, csUnModify, csRemoved, csInserted, csAll, csNormal);
TatcDBGrid = class(TDBGrid)
private
FCachedShow: TCachedShow;
FModifiedColor: TColor;
FInsertedColor: TColor;
FDeletedColor: TColor;
procedure SetCachedShow(const Value: TCachedShow);
protected
procedure DrawDataCell(const Rect: TRect; Field: TField;
State: TGridDrawState); override;
procedure DrawColumnCell(const Rect: TRect; DataCol: Integer;
Column: TColumn; State: TGridDrawState); override;
public
constructor Create(AOwner: TComponent); override;
published
property atcCachedShow: TCachedShow read FCachedShow write SetCachedShow;
property atcDeletedColor: TColor read FDeletedColor write FDeletedColor;
property atcInsertedColor: TColor read FInsertedColor write FInsertedColor;
property atcModifiedColor: TColor read FModifiedColor write FModifiedColor;
end;
procedure Register;
implementation
(******************************************************************************)
procedure Register;
begin
RegisterComponents('ATC DB Compo', [TatcDBGrid]);
end;
(******************************************************************************)
constructor TatcDBGrid.Create(AOwner: TComponent);
(*
Description: Record Type Showing is All except Deletes.
*)
begin
inherited;
FCachedShow := csNormal;
FDeletedColor := clGray;
FInsertedColor := clAqua;
FModifiedColor := clRed;
end;
(******************************************************************************)
procedure TatcDBGrid.DrawColumnCell(const Rect: TRect; DataCol: Integer;
Column: TColumn; State: TGridDrawState);
(*
Description: On Drawing Column Color Updated Records.
*)
var
ARect: TRect;
begin
inherited;
if not Assigned(Column.Field) then
exit;
// Copy Rect into Variable.
CopyRect(ARect, Rect);
if Assigned(DataLink) and (DataLink.Active) and (DataLink.DataSet <> nil) then
begin
// если текущая запись изменена
if DataLink.DataSet.UpdateStatus = usModified then
begin
Canvas.Brush.Color := atcModifiedColor;
Canvas.Font.Color := clBlack;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Column.Field.Text), Length(Column.Field.Text), ARect,
AlignFlags[Column.Alignment] or RTL[UseRightToLeftAlignmentForField(Column.Field, Column.Alignment)]);
end
// если текущая запись добавлена.
else if DataLink.DataSet.UpdateStatus = usInserted then
begin
Canvas.Brush.Color := atcInsertedColor;
Canvas.Font.Color := clBlack;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Column.Field.Text), Length(Column.Field.Text), ARect,
AlignFlags[Column.Alignment] or RTL[UseRightToLeftAlignmentForField(Column.Field, Column.Alignment)]);
end
// если текущая запись удалена.
else if DataLink.DataSet.UpdateStatus = usDeleted then
begin
Canvas.Brush.Color := atcDeletedColor;
Canvas.Font.Color := clWhite;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Column.Field.Text), Length(Column.Field.Text), ARect,
AlignFlags[Column.Alignment] or RTL[UseRightToLeftAlignmentForField(Column.Field, Column.Alignment)]);
end;
end;
end;
(******************************************************************************)
procedure TatcDBGrid.DrawDataCell(const Rect: TRect; Field: TField;
State: TGridDrawState);
(*
Описание: Рисуем ячейки
*)
var
ARect: TRect;
begin
inherited;
CopyRect(ARect, Rect);
if Assigned(DataLink) and (DataLink.Active) and (DataLink.DataSet <> nil) then
begin
// если текущая запись изменена
if DataLink.DataSet.UpdateStatus = usModified then
begin
Canvas.Brush.Color := clRed;
Canvas.Font.Color := clBlack;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Field.Text), Length(Field.Text), ARect,
AlignFlags[Field.Alignment] or RTL[UseRightToLeftAlignmentForField(Field, Field.Alignment)]);
end
// если текущая запись добавлена.
else if DataLink.DataSet.UpdateStatus = usInserted then
begin
Canvas.Brush.Color := clAqua;
Canvas.Font.Color := clBlack;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Field.Text), Length(Field.Text), ARect,
AlignFlags[Field.Alignment] or RTL[UseRightToLeftAlignmentForField(Field, Field.Alignment)]);
end
// если текущая запись удалена.
else if DataLink.DataSet.UpdateStatus = usDeleted then
begin
Canvas.Brush.Color := clGray;
Canvas.Font.Color := clWhite;
Canvas.FillRect(Rect);
DrawText(Canvas.Handle, PChar(Field.Text), Length(Field.Text), ARect,
AlignFlags[Field.Alignment] or RTL[UseRightToLeftAlignmentForField(Field, Field.Alignment)]);
end;
end;
end;
(******************************************************************************)
procedure TatcDBGrid.SetCachedShow(const Value: TCachedShow);
(*
Description: Record type for showing in grid.
Parameters: Value cached record show.
*)
begin
FCachedShow := Value;
if ComponentState = [csDesigning] then
exit;
if not Assigned(DataSource) or not Assigned(DataSource.DataSet) then
exit;
// для показа только выбранного типа записей.
if Assigned(DataLink) and Assigned(DataLink.DataSet) and (DataLink.Active) then
begin
case FCachedShow of
csAll:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtModified, rtInserted, rtDeleted, rtUnmodified];
csModify:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtModified];
csUnModify:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtUnmodified];
csInserted:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtInserted];
csRemoved:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtDeleted];
csNormal:
TBDEDataSet(DataSource.DataSet).UpdateRecordTypes := [rtModified, rtInserted, rtUnmodified];
end;
end;
end;
(******************************************************************************)
end.
Взято с Исходников.ru
DDE
DDE
Cодержание раздела:
Декомпиляция в Delphi
Декомпиляция в Delphi
Декомпиляция в Delphi
(перевод одноимённой статьи с delphi.about.com ) Читая форумы по программированию, иногда натыкаешься на вопрос типа: "У меня есть откомпилированная программа на Delphi. Как мне получить её исходный код?". Обычно такой вопрос возникает, когда программист потерял файлы проекта и у него остался только .exe. Как правило полностью восстановить исходный код на языке высокого уровня невозможно. Значит ли это, что другие тоже не смогут восстановить исходный код Вашей программы ? Хм ... и да и нет ...
Для начала сразу скажу, что восстановить исходный код в точности каким он был однозначно невозможно, так как не существует в мире такого декомпилятора, который бы смог сотворить такое.
После компиляции и линковки проекта и получения исполняемого файла все имена, используемые в программе конвертируются в адреса. Потеря имён означет, что декомпилятор создаст уникальное имя для каждой константы, переменной, функции и процедуры. Даже если мы и достигнем какого-то успеха в декомпиляции исполняемого файла, то получим уже другой синтаксис программы. Данная проблема связана с тем, что при компиляции практически идентичные куски кода могут быть скомпилированы в разные последовательности машинных команд (ASM), которые присутствуют в .exe файле. Естевственно декомпилятор не обладает такой степенью интеллектуальности, чтобы решить - какова же была последовательность инструкций языка высокого уровня в исходном проекте.
Когда же применяется декомпиляция ? Для этого существует довольно много причин. Вот некторые из них:
- Восстановление исходного кода;
- Перенос приложения на другую платформу;
- Определение наличия вирусов в коде программы или вредоносного кода;
- Исправление ошибок в программе, в случае, если создатель приложения не собирается этого делать :)
Легально ли всё это ? Хотя декомпиляция и не является взломом, но утвердительно ответить на этот вопрос довольно сложно. Обычно программы защищены законом об авторских правах, однако в большинстве стран на декомпиляцию делается исключение. В часности, когда необходимо изменить интерфейс программы для конкретной страны, а сервис приложения не позволяет этого сделать.
На данный момент Borland не предоставляет никаких программных продуктов, способных декомпилировать исполняемые файлы (.exe) либо откомпилированные Delphi-модули (.dcu) в исходный код (.pas).
Если же Вы всё-таки решились попробовать декомпилировать исполняемый файл, то необходимо знать следующие вещи. Исходные коды на Delphi обычно хранятся в файлах двух типов: сам исходник в ASCII кодировке (.pas, .dpr) и файлы ресурсов (.res, .rc, .dfm, .dcr). Dfm файлы хранят в себе свойства объектов, содержащихся в форме. При создании конечного .exe, Delphi копирует в него информацию из .dfm файлов. Каждый раз, когда мы изменяем координаты формы, описания кнопок или связанные с ними события, то Delphi записывает эти изменения в .dfm (за исключением кода процедур. Он сохраняется в файлах pas/dcu ). И наконец, чтобы получить при декомпиляции файл .dfm, нужно знать - какие типы ресурсов хранятся внутри Win32 исполняемого модуля.
Все программы, скомпилированные в Delphi имеют следующие секции: CODE, DATA, BSS, .idata, tls, .rdata, .rsrc. Самые важные для декомпиляции секции CODE и .rsrc. В статье "Adding functionality to a Delphi program" приведены некоторые интересные факты о исполняемых форматах Delphi, а так же информация о классах и DFM ресурсах. В этой статье есть один интересный момент под заголовком: "Как добавить свой обработчик события в уже откомпилированный файл, например, чтобы изменять тект на кнопке".
Среди многих типов ресурсов, которые сохранены в .exe файле, интерес представляет RT_RCDATA, который хранит информацию, которая были в DFM файле перед трансляцией. Чтобы извлеч DFM данные из .exe файла, мы можем вызываться API функцией EnumResourceNames.
Исскуство декомпилирования традиционно было уделом мастеров, знакомых с ассемблером и отладчиками. Некоторые Delphi декомпиляторы создают впечатление, что любой, даже с ограниченными техническими знаниями, может изменить по своему желанию большинство исполняемых файлов Delphi.
И в заключение, если Вы заинтересовались декомпилованием, то предлагаю Вам несколько Delphi декомпиляторов:
DeDe
DeDe довольно шустрая программка, позволяющая анализировать экзешники, скомпилированные в Delphi. После декомпиляции DeDe даёт Вам следующее:
- Все dfm файлы. Вы сможете открывать их и редактировать в Delphi
- Все объявленные методы с хорошо комментированным кодом на ассемблере с ссылками на строки, импортированных функций, методов и компонент в юните, блоки Try-Except и Try-Finally.
- Большое количество дополнительной информации.
- Вы можете создать папку Delphi проекта со всеми файлами dfm, pas, dpr. Не забудьте, что pas файлы содержат ассемблерный код.
Revendepro
Revendepro находит почти все структуры (классы, типы, процедуры, и т.д.) в программе, и генерирует их паскальное представление, процедуры естевственно будут представлены на языке ассемблера. К сожалению, полученный ассемблерный код не может быть заново откомпилирован. Так же доступен исходник этого декомпилятора. К сожалению, этот декомпилятор не совсем рабочий - генерирует ошибку при декомпиляции.
MRIP
Позволяет извлекать из Delphi приложения любые ресурсы: курсоры, иконки, dfm файлы, pas файлы и т.д. Но главная его особенность - это способность извлекать файлы, хранящиеся в других файлах. Поддерживается более 100 форматов файлов. MRip работает под DOS.
Exe2Dpr
Эта программа может восстановить частично потерянные исходники проекта. Не имеет интерфейса и работает с командной строки, например: 'exe2dpr [-o] exeFile' ( исходники проекта будут созданы в текущей директории).
Взято с Исходников.ru
Декомпилляция звукового файла формата Wave и получение звуковых данных
Декомпилляция звукового файла формата Wave и получение звуковых данных
Интересно, есть ли технология преобразования Wave-формата в обычный набор звуковых данных? К примеру, мне необходимо удалить заголовок и механизм (метод) сжатия, которые могут компилироваться и сохраняться вместе с Wave-файлами.
У меня есть программа под D1/D2, которая читает WAV-файлы и вытаскивает исходные данные, но она не может их восстанавить, используя зашитый алгоритм сжатия.
unitLinearSystem;
interface
{============== Тип, описывающий формат WAV ==================}
type
WAVHeader = record
nChannels: Word;
nBitsPerSample: LongInt;
nSamplesPerSec: LongInt;
nAvgBytesPerSec: LongInt;
RIFFSize: LongInt;
fmtSize: LongInt;
formatTag: Word;
nBlockAlign: LongInt;
DataSize: LongInt;
end;
{============== Поток данных сэмпла ========================}
const
MaxN = 300; { максимальное значение величины сэмпла }
type
SampleIndex = 0..MaxN + 3;
type
DataStream = array[SampleIndex] of Real;
var
N: SampleIndex;
{============== Переменные сопровождения ======================}
type
Observation = record
Name: string[40]; {Имя данного сопровождения}
yyy: DataStream; {Массив указателей на данные}
WAV: WAVHeader; {Спецификация WAV для сопровождения}
Last: SampleIndex; {Последний доступный индекс yyy}
MinO, MaxO: Real; {Диапазон значений yyy}
end;
var
K0R, K1R, K2R, K3R: Observation;
K0B, K1B, K2B, K3B: Observation;
{================== Переменные имени файла ===================}
var
StandardDatabase: string[80];
BaseFileName: string[80];
StandardOutput: string[80];
StandardInput: string[80];
{=============== Объявления процедур ==================}
procedure ReadWAVFile(var Ki, Kj: Observation);
procedure WriteWAVFile(var Ki, Kj: Observation);
procedure ScaleData(var Kk: Observation);
procedure InitAllSignals;
procedure InitLinearSystem;
implementation
{$R *.DFM}
uses VarGraph, SysUtils;
{================== Стандартный формат WAV-файла ===================}
const
MaxDataSize: LongInt = (MaxN + 1) * 2 * 2;
const
MaxRIFFSize: LongInt = (MaxN + 1) * 2 * 2 + 36;
const
StandardWAV: WAVHeader = (
nChannels: Word(2);
nBitsPerSample: LongInt(16);
nSamplesPerSec: LongInt(8000);
nAvgBytesPerSec: LongInt(32000);
RIFFSize: LongInt((MaxN + 1) * 2 * 2 + 36);
fmtSize: LongInt(16);
formatTag: Word(1);
nBlockAlign: LongInt(4);
DataSize: LongInt((MaxN + 1) * 2 * 2)
);
{================== Сканирование переменных сопровождения ===================}
procedure ScaleData(var Kk: Observation);
var
I: SampleIndex;
begin
{Инициализация переменных сканирования}
Kk.MaxO := Kk.yyy[0];
Kk.MinO := Kk.yyy[0];
{Сканирование для получения максимального и минимального значения}
for I := 1 to Kk.Last do
begin
if Kk.MaxO < Kk.yyy[I] then
Kk.MaxO := Kk.yyy[I];
if Kk.MinO > Kk.yyy[I] then
Kk.MinO := Kk.yyy[I];
end;
end; { ScaleData }
procedure ScaleAllData;
begin
ScaleData(K0R);
ScaleData(K0B);
ScaleData(K1R);
ScaleData(K1B);
ScaleData(K2R);
ScaleData(K2B);
ScaleData(K3R);
ScaleData(K3B);
end; {ScaleAllData}
{================== Считывание/запись WAV-данных ===================}
var
InFile, OutFile: file of Byte;
type
Tag = (F0, T1, M1);
type
FudgeNum = record
case X: Tag of
F0: (chrs: array[0..3] of Byte);
T1: (lint: LongInt);
M1: (up, dn: Integer);
end;
var
ChunkSize: FudgeNum;
procedure WriteChunkName(Name: string);
var
i: Integer;
MM: Byte;
begin
for i := 1 to 4 do
begin
MM := ord(Name[i]);
write(OutFile, MM);
end;
end; {WriteChunkName}
procedure WriteChunkSize(LL: Longint);
var
I: integer;
begin
ChunkSize.x := T1;
ChunkSize.lint := LL;
ChunkSize.x := F0;
for I := 0 to 3 do
Write(OutFile, ChunkSize.chrs[I]);
end;
procedure WriteChunkWord(WW: Word);
var
I: integer;
begin
ChunkSize.x := T1;
ChunkSize.up := WW;
ChunkSize.x := M1;
for I := 0 to 1 do
Write(OutFile, ChunkSize.chrs[I]);
end; {WriteChunkWord}
procedure WriteOneDataBlock(var Ki, Kj: Observation);
var
I: Integer;
begin
ChunkSize.x := M1;
with Ki.WAV do
begin
case nChannels of
1: if nBitsPerSample = 16 then
begin {1..2 Помещаем в буфер одноканальный 16-битный сэмпл}
ChunkSize.up := trunc(Ki.yyy[N] + 0.5);
if N < MaxN then
ChunkSize.dn := trunc(Ki.yyy[N + 1] + 0.5);
N := N + 2;
end
else
begin {1..4 Помещаем в буфер одноканальный 8-битный сэмпл}
for I := 0 to 3 do
ChunkSize.chrs[I]
:= trunc(Ki.yyy[N + I] + 0.5);
N := N + 4;
end;
2: if nBitsPerSample = 16 then
begin {2 Двухканальный 16-битный сэмпл}
ChunkSize.dn := trunc(Ki.yyy[N] + 0.5);
ChunkSize.up := trunc(Kj.yyy[N] + 0.5);
N := N + 1;
end
else
begin {4 Двухканальный 8-битный сэмпл}
ChunkSize.chrs[1] := trunc(Ki.yyy[N] + 0.5);
ChunkSize.chrs[3] := trunc(Ki.yyy[N + 1] + 0.5);
ChunkSize.chrs[0] := trunc(Kj.yyy[N] + 0.5);
ChunkSize.chrs[2] := trunc(Kj.yyy[N + 1] + 0.5);
N := N + 2;
end;
end; {with WAV do begin..}
end; {четырехбайтовая переменная "ChunkSize" теперь заполнена}
ChunkSize.x := T1;
WriteChunkSize(ChunkSize.lint); {помещаем 4 байта данных}
end; {WriteOneDataBlock}
procedure WriteWAVFile(var Ki, Kj: Observation);
var
MM: Byte;
I: Integer;
OK: Boolean;
begin
{Приготовления для записи файла данных}
AssignFile(OutFile, StandardOutput); { Файл, выбранный в диалоговом окне }
ReWrite(OutFile);
with Ki.WAV do
begin
DataSize := nChannels * (nBitsPerSample div 8) * (Ki.Last + 1);
RIFFSize := DataSize + 36;
fmtSize := 16;
end;
{Записываем ChunkName "RIFF"}
WriteChunkName('RIFF');
{Записываем ChunkSize}
WriteChunkSize(Ki.WAV.RIFFSize);
{Записываем ChunkName "WAVE"}
WriteChunkName('WAVE');
{Записываем tag "fmt_"}
WriteChunkName('fmt ');
{Записываем ChunkSize}
Ki.WAV.fmtSize := 16; {должно быть 16-18}
WriteChunkSize(Ki.WAV.fmtSize);
{Записываем formatTag, nChannels}
WriteChunkWord(Ki.WAV.formatTag);
WriteChunkWord(Ki.WAV.nChannels);
{Записываем nSamplesPerSec}
WriteChunkSize(Ki.WAV.nSamplesPerSec);
{Записываем nAvgBytesPerSec}
WriteChunkSize(Ki.WAV.nAvgBytesPerSec);
{Записываем nBlockAlign, nBitsPerSample}
WriteChunkWord(Ki.WAV.nBlockAlign);
WriteChunkWord(Ki.WAV.nBitsPerSample);
{Записываем метку блока данных "data"}
WriteChunkName('data');
{Записываем DataSize}
WriteChunkSize(Ki.WAV.DataSize);
N := 0; {первая запись-позиция}
while N <= Ki.Last do
WriteOneDataBlock(Ki, Kj); {помещаем 4 байта и увеличиваем счетчик N}
{Освобождаем буфер файла}
CloseFile(OutFile);
end; {WriteWAVFile}
procedure InitSpecs;
begin
end; { InitSpecs }
procedure InitSignals(var Kk: Observation);
var
J: Integer;
begin
for J := 0 to MaxN do
Kk.yyy[J] := 0.0;
Kk.MinO := 0.0;
Kk.MaxO := 0.0;
Kk.Last := MaxN;
end; {InitSignals}
procedure InitAllSignals;
begin
InitSignals(K0R);
InitSignals(K0B);
InitSignals(K1R);
InitSignals(K1B);
InitSignals(K2R);
InitSignals(K2B);
InitSignals(K3R);
InitSignals(K3B);
end; {InitAllSignals}
var
ChunkName: string[4];
procedure ReadChunkName;
var
I: integer;
MM: Byte;
begin
ChunkName[0] := chr(4);
for I := 1 to 4 do
begin
Read(InFile, MM);
ChunkName[I] := chr(MM);
end;
end; {ReadChunkName}
procedure ReadChunkSize;
var
I: integer;
MM: Byte;
begin
ChunkSize.x := F0;
ChunkSize.lint := 0;
for I := 0 to 3 do
begin
Read(InFile, MM);
ChunkSize.chrs[I] := MM;
end;
ChunkSize.x := T1;
end; {ReadChunkSize}
procedure ReadOneDataBlock(var Ki, Kj: Observation);
var
I: Integer;
begin
if N <= MaxN then
begin
ReadChunkSize; {получаем 4 байта данных}
ChunkSize.x := M1;
with Ki.WAV do
case nChannels of
1: if nBitsPerSample = 16 then
begin {1..2 Помещаем в буфер одноканальный 16-битный сэмпл}
Ki.yyy[N] := 1.0 * ChunkSize.up;
if N < MaxN then
Ki.yyy[N + 1] := 1.0 * ChunkSize.dn;
N := N + 2;
end
else
begin {1..4 Помещаем в буфер одноканальный 8-битный сэмпл}
for I := 0 to 3 do
Ki.yyy[N + I] := 1.0 * ChunkSize.chrs[I];
N := N + 4;
end;
2: if nBitsPerSample = 16 then
begin {2 Двухканальный 16-битный сэмпл}
Ki.yyy[N] := 1.0 * ChunkSize.dn;
Kj.yyy[N] := 1.0 * ChunkSize.up;
N := N + 1;
end
else
begin {4 Двухканальный 8-битный сэмпл}
Ki.yyy[N] := 1.0 * ChunkSize.chrs[1];
Ki.yyy[N + 1] := 1.0 * ChunkSize.chrs[3];
Kj.yyy[N] := 1.0 * ChunkSize.chrs[0];
Kj.yyy[N + 1] := 1.0 * ChunkSize.chrs[2];
N := N + 2;
end;
end;
if N <= MaxN then
begin {LastN := N;}
Ki.Last := N;
if Ki.WAV.nChannels = 2 then
Kj.Last := N;
end
else
begin {LastN := MaxN;}
Ki.Last := MaxN;
if Ki.WAV.nChannels = 2 then
Kj.Last := MaxN;
end;
end;
end; {ReadOneDataBlock}
procedure ReadWAVFile(var Ki, Kj: Observation);
var
MM: Byte;
I: Integer;
OK: Boolean;
NoDataYet: Boolean;
DataYet: Boolean;
nDataBytes: LongInt;
begin
if FileExists(StandardInput) then
with Ki.WAV do
begin { Вызов диалога открытия файла }
OK := True; {если не изменится где-нибудь ниже}
{Приготовления для чтения файла данных}
AssignFile(InFile, StandardInput); { Файл, выбранный в диалоговом окне }
Reset(InFile);
{Считываем ChunkName "RIFF"}
ReadChunkName;
if ChunkName <> 'RIFF' then
OK := False;
{Считываем ChunkSize}
ReadChunkSize;
RIFFSize := ChunkSize.lint; {должно быть 18,678}
{Считываем ChunkName "WAVE"}
ReadChunkName;
if ChunkName <> 'WAVE' then
OK := False;
{Считываем ChunkName "fmt_"}
ReadChunkName;
if ChunkName <> 'fmt ' then
OK := False;
{Считываем ChunkSize}
ReadChunkSize;
fmtSize := ChunkSize.lint; {должно быть 18}
{Считываем formatTag, nChannels}
ReadChunkSize;
ChunkSize.x := M1;
formatTag := ChunkSize.up;
nChannels := ChunkSize.dn;
{Считываем nSamplesPerSec}
ReadChunkSize;
nSamplesPerSec := ChunkSize.lint;
{Считываем nAvgBytesPerSec}
ReadChunkSize;
nAvgBytesPerSec := ChunkSize.lint;
{Считываем nBlockAlign}
ChunkSize.x := F0;
ChunkSize.lint := 0;
for I := 0 to 3 do
begin
Read(InFile, MM);
ChunkSize.chrs[I] := MM;
end;
ChunkSize.x := M1;
nBlockAlign := ChunkSize.up;
{Считываем nBitsPerSample}
nBitsPerSample := ChunkSize.dn;
for I := 17 to fmtSize do
Read(InFile, MM);
NoDataYet := True;
while NoDataYet do
begin
{Считываем метку блока данных "data"}
ReadChunkName;
{Считываем DataSize}
ReadChunkSize;
DataSize := ChunkSize.lint;
if ChunkName <> 'data' then
begin
for I := 1 to DataSize do
{пропуск данных, не относящихся к набору звуковых данных}
Read(InFile, MM);
end
else
NoDataYet := False;
end;
nDataBytes := DataSize;
{Наконец, начинаем считывать данные для байтов nDataBytes}
if nDataBytes > 0 then
DataYet := True;
N := 0; {чтение с первой позиции}
while DataYet do
begin
ReadOneDataBlock(Ki, Kj); {получаем 4 байта}
nDataBytes := nDataBytes - 4;
if nDataBytes <= 4 then
DataYet := False;
end;
ScaleData(Ki);
if Ki.WAV.nChannels = 2 then
begin
Kj.WAV := Ki.WAV;
ScaleData(Kj);
end;
{Освобождаем буфер файла}
CloseFile(InFile);
end
else
begin
InitSpecs; {файл не существует}
InitSignals(Ki); {обнуляем массив "Ki"}
InitSignals(Kj); {обнуляем массив "Kj"}
end;
end; { ReadWAVFile }
{================= Операции с набором данных ====================}
const
MaxNumberOfDataBaseItems = 360;
type
SignalDirectoryIndex = 0..MaxNumberOfDataBaseItems;
var
DataBaseFile: file of Observation;
LastDataBaseItem: LongInt; {Номер текущего элемента набора данных}
ItemNameS: array[SignalDirectoryIndex] of string[40];
procedure GetDatabaseItem(Kk: Observation; N: LongInt);
begin
if N <= LastDataBaseItem then
begin
Seek(DataBaseFile, N);
Read(DataBaseFile, Kk);
end
else
InitSignals(Kk);
end; {GetDatabaseItem}
procedure PutDatabaseItem(Kk: Observation; N: LongInt);
begin
if N < MaxNumberOfDataBaseItems then
if N <= LastDataBaseItem then
begin
Seek(DataBaseFile, N);
Write(DataBaseFile, Kk);
LastDataBaseItem := LastDataBaseItem + 1;
end
else
while LastDataBaseItem <= N do
begin
Seek(DataBaseFile, LastDataBaseItem);
Write(DataBaseFile, Kk);
LastDataBaseItem := LastDataBaseItem + 1;
end
else
ReportError(1); {Попытка чтения MaxNumberOfDataBaseItems}
end; {PutDatabaseItem}
procedure InitDataBase;
begin
LastDataBaseItem := 0;
if FileExists(StandardDataBase) then
begin
Assign(DataBaseFile, StandardDataBase);
Reset(DataBaseFile);
while not EOF(DataBaseFile) do
begin
GetDataBaseItem(K0R, LastDataBaseItem);
ItemNameS[LastDataBaseItem] := K0R.Name;
LastDataBaseItem := LastDataBaseItem + 1;
end;
if EOF(DataBaseFile) then
if LastDataBaseItem > 0 then
LastDataBaseItem := LastDataBaseItem - 1;
end;
end; {InitDataBase}
function FindDataBaseName(Nstg: string): LongInt;
var
ThisOne: LongInt;
begin
ThisOne := 0;
FindDataBaseName := -1;
while ThisOne < LastDataBaseItem do
begin
if Nstg = ItemNameS[ThisOne] then
begin
FindDataBaseName := ThisOne;
Exit;
end;
ThisOne := ThisOne + 1;
end;
end; {FindDataBaseName}
{======================= Инициализация модуля ========================}
procedure InitLinearSystem;
begin
BaseFileName := '\PROGRA~1\SIGNAL~1\';
StandardOutput := BaseFileName + 'K0.wav';
StandardInput := BaseFileName + 'K0.wav';
StandardDataBase := BaseFileName + 'Radar.sdb';
InitAllSignals;
InitDataBase;
ReadWAVFile(K0R, K0B);
ScaleAllData;
end; {InitLinearSystem}
begin {инициализируемый модулем код}
InitLinearSystem;
end. {Unit LinearSystem}
Взято с
Дельфи IAutoComplete интерфейс
Дельфи IAutoComplete интерфейс
unituAutoComplete;
interface
uses
Windows, SysUtils, Controls, Classes, ActiveX, ComObj, stdctrls, Forms, Messages;
const
IID_IAutoComplete: TGUID = '{00bb2762-6a77-11d0-a535-00c04fd7d062}';
IID_IAutoComplete2: TGUID = '{EAC04BC0-3791-11d2-BB95-0060977B464C}';
CLSID_IAutoComplete: TGUID = '{00BB2763-6A77-11D0-A535-00C04FD7D062}';
IID_IACList: TGUID = '{77A130B0-94FD-11D0-A544-00C04FD7d062}';
IID_IACList2: TGUID = '{470141a0-5186-11d2-bbb6-0060977b464c}';
CLSID_ACLHistory: TGUID = '{00BB2764-6A77-11D0-A535-00C04FD7D062}';
CLSID_ACListISF: TGUID = '{03C036F1-A186-11D0-824A-00AA005B4383}';
CLSID_ACLMRU: TGUID = '{6756a641-de71-11d0-831b-00aa005b4383}';
type
IACList = interface(IUnknown)
['{77A130B0-94FD-11D0-A544-00C04FD7d062}']
function Expand(pszExpand: POLESTR): HResult; stdcall;
end;
const
{Options for IACList2}
ACLO_NONE = 0; {don't enumerate anything}
ACLO_CURRENTDIR = 1; {enumerate current directory}
ACLO_MYCOMPUTER = 2; {enumerate MyComputer}
ACLO_DESKTOP = 4; {enumerate Desktop Folder}
ACLO_FAVORITES = 8; {enumerate Favorites Folder}
ACLO_FILESYSONLY = 16; {enumerate only the file system}
type
IACList2 = interface(IACList)
['{470141a0-5186-11d2-bbb6-0060977b464c}']
function SetOptions(dwFlag: DWORD): HResult; stdcall;
function GetOptions(var pdwFlag: DWORD): HResult; stdcall;
end;
IAutoComplete = interface(IUnknown)
['{00bb2762-6a77-11d0-a535-00c04fd7d062}']
function Init(hwndEdit: HWND; const punkACL: IUnknown; pwszRegKeyPath,
pwszQuickComplete: POLESTR): HResult; stdcall;
function Enable(fEnable: BOOL): HResult; stdcall;
end;
const
{Options for IAutoComplete2}
ACO_NONE = 0;
ACO_AUTOSUGGEST = $1;
ACO_AUTOAPPEND = $2;
ACO_SEARCH = $4;
ACO_FILTERPREFIXES = $8;
ACO_USETAB = $10;
ACO_UPDOWNKEYDROPSLIST = $20;
ACO_RTLREADING = $40;
type
IAutoComplete2 = interface(IAutoComplete)
['{EAC04BC0-3791-11d2-BB95-0060977B464C}']
function SetOptions(dwFlag: DWORD): HResult; stdcall;
function GetOptions(out pdwFlag: DWORD): HResult; stdcall;
end;
TEnumString = class(TInterfacedObject, IEnumString)
private
FStrings: TStringList;
FCurrIndex: integer;
public
{IEnumString}
function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; stdcall;
function Skip(celt: Longint): HResult; stdcall;
function Reset: HResult; stdcall;
function Clone(out enm: IEnumString): HResult; stdcall;
{VCL}
constructor Create;
destructor Destroy; override;
end;
TACOption = (acAutoAppend, acAutoSuggest, acUseArrowKey);
TACOptions = set of TACOption;
TACSource = (acsList, acsHistory, acsMRU, acsShell);
TACEdit = class(TEdit)
private
FACList: TEnumString;
FAutoComplete: IAutoComplete;
FACEnabled: boolean;
FACOptions: TACOptions;
FACSource: TACSource;
function GetACStrings: TStringList;
procedure SetACEnabled(const Value: boolean);
procedure SetACOptions(const Value: TACOptions);
procedure SetACSource(const Value: TACSource);
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property ACStrings: TStringList read GetACStrings;
property ACEnabled: boolean read FACEnabled write SetACEnabled;
property ACOptions: TACOptions read FACOptions write SetACOptions;
property ACSource: TACSource read FACSource write SetACSource;
end;
implementation
{ IUnknownInt }
function TEnumString.Clone(out enm: IEnumString): HResult;
begin
Result := E_NOTIMPL;
pointer(enm) := nil;
end;
constructor TEnumString.Create;
begin
inherited Create;
FStrings := TStringList.Create;
FCurrIndex := 0;
end;
destructor TEnumString.Destroy;
begin
FStrings.Free;
inherited;
end;
function TEnumString.Next(celt: Integer; out elt; pceltFetched: PLongint): HResult;
var
I: Integer;
wStr: WideString;
begin
I := 0;
while (I < celt) and (FCurrIndex < FStrings.Count) do
begin
wStr := FStrings[FCurrIndex];
TPointerList(elt)[I] := CoTaskMemAlloc(2 * (Length(wStr) + 1));
StringToWideChar(wStr, TPointerList(elt)[I], 2 * (Length(wStr) + 1));
Inc(I);
Inc(FCurrIndex);
end;
if pceltFetched <> nil then
pceltFetched^ := I;
if I = celt then
Result := S_OK
else
Result := S_FALSE;
end;
function TEnumString.Reset: HResult;
begin
FCurrIndex := 0;
Result := S_OK;
end;
function TEnumString.Skip(celt: Integer): HResult;
begin
if (FCurrIndex + celt) <= FStrings.Count then
begin
Inc(FCurrIndex, celt);
Result := S_OK;
end
else
begin
FCurrIndex := FStrings.Count;
Result := S_FALSE;
end;
end;
{ TACEdit }
constructor TACEdit.Create(AOwner: TComponent);
begin
inherited;
FACList := TEnumString.Create;
FACEnabled := true;
FACOptions := [acAutoAppend, acAutoSuggest, acUseArrowKey];
end;
procedure TACEdit.CreateWnd;
var
Dummy: IUnknown;
Strings: IEnumString;
begin
inherited;
if HandleAllocated then
begin
try
Dummy := CreateComObject(CLSID_IAutoComplete);
if (Dummy <> nil) and (Dummy.QueryInterface(IID_IAutoComplete, FAutoComplete) =
S_OK) then
begin
case FACSource of
acsHistory:
Strings := CreateComObject(CLSID_ACLHistory) as IEnumString;
acsMRU:
Strings := CreateComObject(CLSID_ACLMRU) as IEnumString;
acsShell:
Strings := CreateComObject(CLSID_ACListISF) as IEnumString;
else
Strings := FACList as IEnumString;
end;
if S_OK = FAutoComplete.Init(Handle, Strings, nil, nil) then
begin
SetACEnabled(FACEnabled);
SetACOptions(FACOptions);
end;
end;
except
{CLSID_IAutoComplete is not available}
end;
end;
end;
destructor TACEdit.Destroy;
begin
FACList := nil;
inherited;
end;
procedure TACEdit.DestroyWnd;
begin
if (FAutoComplete <> nil) then
begin
FAutoComplete.Enable(false);
FAutoComplete := nil;
end;
inherited;
end;
function TACEdit.GetACStrings: TStringList;
begin
Result := FACList.FStrings;
end;
procedure TACEdit.SetACEnabled(const Value: boolean);
begin
if (FAutoComplete <> nil) then
begin
FAutoComplete.Enable(FACEnabled);
end;
FACEnabled := Value;
end;
procedure TACEdit.SetACOptions(const Value: TACOptions);
const
Options: array[TACOption] of integer = (ACO_AUTOAPPEND, ACO_AUTOSUGGEST,
ACO_UPDOWNKEYDROPSLIST);
var
Option: TACOption;
Opt: DWORD;
AC2: IAutoComplete2;
begin
if (FAutoComplete <> nil) then
begin
if S_OK = FAutoComplete.QueryInterface(IID_IAutoComplete2, AC2) then
begin
Opt := ACO_NONE;
for Option := Low(Options) to High(Options) do
begin
if (Option in FACOptions) then
Opt := Opt or DWORD(Options[Option]);
end;
AC2.SetOptions(Opt);
end;
end;
FACOptions := Value;
end;
procedure TACEdit.SetACSource(const Value: TACSource);
begin
if FACSource <> Value then
begin
FACSource := Value;
RecreateWnd;
end;
end;
initialization
finalization
end.
Взято с
Delphi Knowledge BaseДельфи компонент для подкраски синтаксиса
Дельфи компонент для подкраски синтаксиса
Результат совместной работы Fanasist'а и меня. Это компонент для Дельфи для известного пакета SynEdit (http://synedit.sourceforge.net), позволяющий на лету создавать подкраску синтаксиса по любым правилам любых форматов (создание и загрузка в run-time, хранение шаблонов на диске). В настоящее время пакет включает в себя более 300 готовых шаблонов для наиболее распространённых форматов, но каждый может создать свой собственный шаблон (можно с помошью компонента или используя прилагающуюся утилиту). Пример использования - простенький текстовый редактор с поддержкой любых расскрасок.
Загрузить можно с:
Платформа: Delphi 5/6
Для работы необходимо установить предварительно установить пакет SynEdit ().
Компонент свободен для распространения и поставляется с исходными кодами.
Логика работы:
Для подкраски синтаксиса необходимо предусмотреть следующие правила:
1) расскраска всего кода в промежутке от одного слова до второго - например комментарии /*...*/ или строки "...."
2) расскраска ключевых слов
Это реализовано, кроме того бывают ситуации когда промежуток должен быть расскрашен по другому с другими правилами например ASP код внутри HTML или ассемблерные вставки внутри Дельфи - это тоже реализовано тем что внутри промежутка можно использовать свои правила и промежутки. Вложенность не лимитирована.
Всем кому интересно, я создал форум для обсуждения компонента, правда сообщения писать только на английском, там уже идёт дисскуссия с авторами проекта SynEdit:
Автор Vit
Delphi 4 виснут при запуске. Видеокарта S3 Virge?
Delphi 4 виснут при запуске. Видеокарта S3 Virge?
REGEDIT4
[HKEY_CURRENT_CONFIG\Display\Settings]
"BusThrottle"="on"
Если не помогает, то попробуйте добавить в system.ini:
[Display]
"BusThrottle"="On"
Взято с сайта
Надо уменьшить степень аппаратного ускорения графики в свойствах компьютера
Автор Vit
Delphi 7 and FastNet Components
Delphi 7 and FastNet Components
Where are the FastNet Components in Delphi 7?
The Fastnet components are no longer bundled with Delphi. It appears Netmasters does have a version compatible with Delphi 7 which can be purchased at: http://www.netmastersllc.com/
Delphi 7. Отличия и особенности.
Delphi 7. Отличия и особенности.
Отличий немного, а стоит ли переходить это скорее всего надо по обстоятельствам решать.
1) Исчезли компоненты NetMaster и изменился репортинг - если в поддерживаемых/разрабатываемых программах они используются то переход будет не столь простым
2) Очень многие третьесторонние библиотеки пока не имеют версий для 7х Дельфи - следовательно если они используются то надо ждать
3) Очень много добавилось для поддержки Web/Internet/Soap/XML и т.п. если используются новые интернет технологии то быстрый переход на Дельфи 7 может быть весьма оправдан.
4) Если используется Дельфи для построения стандартных виндовых приложений то отличий можно сказать что почти нет, переход либо можно не делать, либо сделать - так как он будет очень простой.
5) Если Вы разрабатываете компоненты, то естественно нужна поддержка и этой версии
Автор Vit
Взято с Vingrad.ru
и кстати ещё исчезли компоненты TclienSocket и TServerSocket
Автор Radmin
Взято с Vingrad.ru
См. также другие статьи:
Delphi 7 ToolsAPI: Компоненты
Delphi 7 ToolsAPI: Компоненты
Создание pop-up меню своего компонента и кое-что еще о классе TComponentExpert
Давайте рассмотрим создание простейшего одноуровневого контекстного меню на своем компоненте, которое будет открываться при щелчке правой кнопкой по нему в самом верху контекстного меню Delphi.
Прежде всего вам следует разделить код вашего компонента на Design-time и Run-time. Для этого перенесите ваш компонент в модуль, с названием, например, MyComponent.pas, а процедуры регистрации его в палитре компонентов (procedure Register и т.д.) в модуль, с названием, например, MyComponentReg. На такие меры приходится идти из-за того, что Borland не включила в исходные коды исходник файла Proxies.pas.
Итак, получим два файла:
MyComponent.pas:
unit MyComponent;
interface
uses
SysUtils, Classes;
type
TMyComponent = class(TComponent)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
MyComponentReg.pas
unit MyComponentReg;
interface
uses DesignIntf, DesignEditors, MyComponent, Classes, Dialogs;
type
TMyComponentEditor = class(TComponentEditor)
private
procedure ExecuteVerb(Index: Integer); override;
function GetVerbCount: Integer; override;
function GetVerb(Index: Integer): string; override;
procedure Edit; override;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TMyComponent]);
RegisterComponentEditor(TMyComponent, TMyComponentEditor);
end;
{ TMyComponentEditor }
procedure TMyComponentEditor.Edit;
begin
ShowMessage('TMyComponent component v1.0 by Rastrusny Vladislav');
end;
procedure TMyComponentEditor.ExecuteVerb(Index: Integer);
begin
inherited;
case Index of
0: //Действие при выборе первого определенного пункта меню
end;
end;
function TMyComponentEditor.GetVerb(Index: Integer): string;
begin
case Index of
0: Result := 'Demo Menu Item 1'; //Название первого пункта меню
end;
end;
function TMyComponentEditor.GetVerbCount: Integer;
begin
Result := 1;
end;
end.
Рассмотрим теперь, что же тут написано. В первом файле просто определен компонент MyComponent. В нем вы определяете все свойства и методы вашего компонента. Все как обычно. Теперь - второй файл MyComponentReg. Он содержит процедуры регистрации компонента и процедуру регистрации редактора компонента (TComponentEditor). Этот редактор и будет отображать меню и прочие безобразия. Итак:
Определяем TMyComponentEditor как потомка TComponentEditor. Сам по себе этот класс является "воплотителем" интерфейса IComponentEditor, хотя нам все равно. Для того, чтобы все это заработало нам нужно будет переопределить стандартные методы класса TComponentEditor. Рассмотрим его:
type
TComponentEditor = class(TBaseComponentEditor, IComponentEditor)
private
FComponent: TComponent;
FDesigner: IDesigner;
public
constructor Create(AComponent: TComponent; ADesigner: IDesigner); override;
procedure Edit; virtual;
function GetVerbCount: Integer; virtual;
function GetVerb(Index: Integer): string; virtual;
procedure ExecuteVerb(Index: Integer); virtual;
procedure Copy; virtual;
procedure PrepareItem(Index: Integer; const AItem: IMenuItem); virtual;
property Component: TComponent;
property Designer: IDesigner;
end;
Конструктор нам переопределять не нужно. Поэтому начнем с описания метода Edit.
Метод Edit вызывается при двойном щелчке по компоненту. Вот так просто! При двойном щелчке на компоненте! Если метод не определен, то при двойном щелчке будет выполнен первый пункт меню, которое вы определили.
Метод GetVerbCount: Integer должен возвращать количество определенных вами пунктов меню.
Метод GetVerb(Index: Integer): string должен возвращать название пункта меню № Index.
Метод ExecuteVerb(Index: Integer) вызывается при щелчке на пункте меню, определенном вами. Index - номер меню из метода GetVerb. В нем вы определяете действия, которые будут происходить при нажатии на ваш пункт меню.
Метод Copy вызывается при копировании вашего компонента в буфер обмена
Свойство Component как вы уже наверное догадались позволяет получить доступ к компоненту, на котором щелкнули мышью и т.п.
Метод PrepareItem(Index: Integer; const AItem: IMenuItem) вызывается для каждого определенного вами пункта меню № Index и через параметр AItem передает сам пункт меню для настройки. Для работы нам нужно будет рассмотреть саму реализацию интерфейса IMenuItem. Он определен в модуле DesignMenus.pas и является потомком интерфейса IMenuItems.
IMenuItems = interface
['{C9CC6C38-C96A-4514-8D6F-1D121727BFAF}']
// public
function SameAs(const AItem: IUnknown): Boolean;
function Find(const ACaption: WideString): IMenuItem;
function FindByName(const AName: string): IMenuItem;
function Count: Integer;
property Items[Index: Integer]: IMenuItem read GetItem;
procedure Clear;
function AddItem(const ACaption: WideString; AShortCut: TShortCut;
AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent = nil;
hCtx: THelpContext = 0; const AName: string = ''): IMenuItem; overload;
function AddItem(AAction: TBasicAction;
const AName: string = ''): IMenuItem; overload;
function InsertItem(const ACaption: WideString;
AShortCut: TShortCut; AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent = nil;
hCtx: THelpContext = 0; const AName: string = ''): IMenuItem; overload;
function InsertItem(Index: Integer; const ACaption: WideString;
AShortCut: TShortCut; AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent = nil;
hCtx: THelpContext = 0; const AName: string = ''): IMenuItem; overload;
function InsertItem(AAction: TBasicAction;
const AName: string = ''): IMenuItem; overload;
function InsertItem(Index: Integer; AAction: TBasicAction;
const AName: string = ''): IMenuItem; overload;
function AddLine(const AName: string = ''): IMenuItem;
function InsertLine(const AName: string = ''): IMenuItem; overload;
function InsertLine(Index: Integer; const AName: string = ''): IMenuItem; overload;
end;
IMenuItem = interface(IMenuItems)
['{DAF029E1-9592-4B07-A450-A10056A2B9B5}']
// public
function Name: TComponentName;
function MenuIndex: Integer;
function Parent: IMenuItem;
function HasParent: Boolean;
function IsLine: Boolean;
property Caption: WideString;
property Checked: Boolean;
property Enabled: Boolean;
property GroupIndex: Byte;
property HelpContext: THelpContext;
property Hint: string;
property RadioItem: Boolean;
property ShortCut: TShortCut;
property Tag: LongInt;
property Visible: Boolean;
end;
Начнем с конца. Т.е. с IMenuItem. Как видно, почти все члены интерфейса соответствуют членам класса TMenuItem. Т.е. обратившись в методе PrepareItem к AItem.Enabled:=false мы запретим выбор этого элемента меню. Что же касается класса TMenuItems, то они, видимо, предназначены для манипулирования элементом меню в качестве родительского для нескольких других. Думаю, в них опытным путем разобраться тоже не составит труда.
Что же касается процедуры RegisterComponentEditor, то она принимает два параметра: первый - класс компонента, для которого создается редактор свойств и второй - собственно сам класс редактора свойств.
Создание редакторов свойств
Для создания редактора свойств нужно написать класс, унаследованный от TBasePropertyEditor. Но мы рассмотрим более функционального его потомка TPropertyEditor
TPropertyEditor = class(TBasePropertyEditor, IProperty, IProperty70)
protected
procedure SetPropEntry(Index: Integer; AInstance: TPersistent;
APropInfo: PPropInfo); override;
protected
function GetFloatValue: Extended;
function GetFloatValueAt(Index: Integer): Extended;
function GetInt64Value: Int64;
function GetInt64ValueAt(Index: Integer): Int64;
function GetMethodValue: TMethod;
function GetMethodValueAt(Index: Integer): TMethod;
function GetOrdValue: Longint;
function GetOrdValueAt(Index: Integer): Longint;
function GetStrValue: string;
function GetStrValueAt(Index: Integer): string;
function GetVarValue: Variant;
function GetVarValueAt(Index: Integer): Variant;
function GetIntfValue: IInterface;
function GetIntfValueAt(Index: Integer): IInterface;
procedure Modified;
procedure SetFloatValue(Value: Extended);
procedure SetMethodValue(const Value: TMethod);
procedure SetInt64Value(Value: Int64);
procedure SetOrdValue(Value: Longint);
procedure SetStrValue(const Value: string);
procedure SetVarValue(const Value: Variant);
procedure SetIntfValue(const Value: IInterface);
protected
{ IProperty }
function GetEditValue(out Value: string): Boolean;
function HasInstance(Instance: TPersistent): Boolean;
{ IProperty70 }
function GetIsDefault: Boolean; virtual;
public
constructor Create(const ADesigner: IDesigner; APropCount: Integer); override;
destructor Destroy; override;
procedure Activate; virtual;
function AllEqual: Boolean; virtual;
function AutoFill: Boolean; virtual;
procedure Edit; virtual;
function GetAttributes: TpropertyAttributes; virtual;
function GetComponent(Index: Integer): TPersistent;
function GetEditLimit: Integer; virtual;
function GetName: string; virtual;
procedure GetProperties(Proc: TGetPropProc); virtual;
function GetPropInfo: PPropInfo; virtual;
function GetPropType: PTypeInfo;
function GetValue: string; virtual;
function GetVisualValue: string;
procedure GetValues(Proc: TGetStrProc); virtual;
procedure Initialize; override;
procedure Revert;
procedure SetValue(const Value: string); virtual;
function ValueAvailable: Boolean;
property Designer: IDesigner read FDesigner;
property PrivateDirectory: string read GetPrivateDirectory;
property PropCount: Integer read FPropCount;
property Value: string read GetValue write SetValue;
end;
Предположим, нам нужно создать редактор для текстового свойства, при нажатии кнопки "…" в Object Inspector.
Объявим специальный тип этого свойства TMyComponentStringProperty = string;
Далее, в компоненте укажем свойство данного типа property MyProperty: TMyComponentStringProperty, далее в Run-time части компонента (MyComponentReg.pas) объявим класс TMyCSPEditor (в переводе: TMyComponentStringPropertyEditor), унаследовав его от класса TStringProperty, который в свою очередь является потомком рассматриваемого класса TPropertyEditor: type TMyCSPEditor = class(TStringProperty) . Переопределим в нем несколько методов таким образом (фрагменты файла):
type
TVRSIDBListViewExcludeColumnsPropertyEditor = class(TStringProperty)
function GetAttributes: TPropertyAttributes; override;
procedure Edit; override;
end;
procedure TVRSIDBListViewExcludeColumnsPropertyEditor.Edit;
var Text: string;
begin
if InputQuery('Введите строковое значение',Text)=False then Exit;
Self.SetValue(Text);
end;
function TVRSIDBListViewExcludeColumnsPropertyEditor.GetAttributes: TPropertyAttributes;
begin
Result:=[paDialog];
end;
Итак, приступаем к рассмотрению методов класса TPropertyEditor. Начнем с тех, которые мы уже использовали.
Метод Edit. Просто вызывается при щелчке на кнопке "…" в Object Inspector. В TStringProperty не переопределен.
Метод SetValue(Text: string). Должен устанавливать значение свойства в переданную строку. В TStringProperty переопределен. Этот метод вызывается самим Object Inspector, когда пользователь вводит значение поля. Вы можете переопределить этот метод для установки вашего свойства в зависимости от значения, введенного пользователем. Если вы обнаруживаете ошибку в переданном параметре - вызовите исключение.
Метод GetAttributes: TPropertyAttributes. Задает параметры свойства. Рассмотрим их по порядку.
paValueList - указывает, что редактор свойств возвращает список допустимых значений свойства через метод GetValues. В редакторе свойств рядом со свойством появляется раскрывающийся список
paSortList - указывает, что список, возвращенный GetValues нужно сортировать
paSubProperties - указывает, что у свойства имеются подсвойства (типа подсвойства Name у свойства Font класса TFont). Подсвойства, если этот флаг установлен, должны возвращаться методом GetProperties.
paDialog - указывает, что рядом со свойством должна быть кнопка "…", по нажатию которой вызывается метод Edit для редактирования значения свойства. Что мы и указали в нашем примере.
paMultiSelect - Разрешает отображать свойство в Object Inspector, даже если выделено более одного объекта
paAutoUpdate - указывает, что метод SetValue нужно вызывать при каждом изменении значения свойства, а не после нажатия Enter или выхода из Object Inspector (Пример: свойство Caption у формы изменяется одновременно с набором на клавиатуре)
paReadOnly - указывает, что значение через Object Inspector изменить нельзя. Оно устанавливается в классе TClassProperty, от которого унаследованы все классовые редакторы свойств типа TStrings, TFont и т.п. При установке рядом со значением свойства отображается строка, возвращенная методом GetValue и значение это изменить нельзя.
paRevertable - указывает, изменение значения свойства можно отменить. Это не касается вложенных подсвойств.
paFullWidthName - указывает Object Inspector, что прорисовка значения свойства не требуется и можно занять под имя свойства всю длину панели.
paVolatileSubProperties - установка этого значения указывает, что при любом изменении свойства нужно повторить сборку подсвойств (GetProperties)
paVCL - ???
paReference - указывает, что свойство является указателем на что-либо. Используется вместе с paSubProperties для указания отображения объекта, на которое ссылается в качестве подсвойств (TFont).
paNotNestable - указывает, что отображать значение свойства в момент, когда его подсвойства развернуты - небезопасно (этот пункт мне пока непонятен)
Методы GetXXXValue и SetXXXValue. Используются для внутренней установки реального значения свойства. Как правило, используются методом GetValue и SetValue. В принципе, все эти методы уже определены в классе TPropertyEditor, и переопределять их не нужно.
Метод Modified вызывается для указания того факта, что значение свойства изменено. Это метод уже определен в TPropertyEditor и переопределять его не требуется.
Метод GetEditValue возвращает true, если значение можно редактировать
Метод GetIsDefault возвращает true, если значение свойства в текущий момент является значением свойства по умолчанию. Т.е. метод должен возвращать true, если НЕ нужно сохранять значение свойства в .dfm файле.
Метод Activate вызывается при выборе свойства в Object Inspector. При использовании переопределения этого метода для отображения значения свойства исключительно в момент активизации нужно быть осторожным, если указаны параметры свойства paSubProperties и paMultiSelect.
Метод AllEqual вызывается всякий раз, когда выделяется более одного компонента. Если этот метод вернет true, будет вызван метод GetValue, в противоположном случае будет отображена пустая строка. Вызывается только, если указано свойство paMultiSelect. Очевидно, метод должен проверять совпадение свойств у все выбранных компонентов путем опроса метода GetComponent.
Метод AutoFill вызывается для определения, могут ли элементы списка быть выбраны по возрастанию. Указывается, только если указан параметр paValueList.
Метод GetComponent возвращает компонент с заданным индексом из выбранных компонентов.
Метод GetEditLimit возвращает максимальное количество символов, которые можно ввести в текстовое значение свойства. По умолчанию 255.
Метод GetName возвращает имя свойства, в котором знаки подчеркивания заменены на пробелы. Свойство метод должен переопределяться только, если свойство не предназначено для отображения в Object Inspector
Метод GetComponentValue возвращает значение свойства типа TComponent в том и только в том случае, если свойство унаследовано от TComponent. Этот метод переопределяется в классе TComponentEditor
Метод GetProperties вызывается для каждого подсвойства, которое редактируется. В метод передается параметр типа TGetPropertyProc. Это указатель на процедуру для обработки каждого свойства. Например, TClassProperty вызывает процедуру TGetPropertyProc для каждого published элемента класса, а TSetProperty - для каждого элемента множества. Т.е. при использовании подсвойств вы должны определить процедуру TGetPropertyProc, чтобы она определяла каждое подсвойство.
Метод GetPropType возвращает указатель на информацию о типе редактируемого свойства (TypeInfo (Type))
Метод GetValue возвращает значение свойства в виде текстовой строки. Например, в TClassProperty этот метод переопределен для возвращения в качестве результата имени типа класса (TStrings и т.п.).
Метод ValueAvailable возвращает true, если можно получить доступ к значению свойства, не вызывая исключения.
Описания для остальных методов и свойств, к сожалению, найти не удалось, поэтому исследовать их можно только опытным путем.
По завершении создания редактора свойств не забудьте зарегистрировать его внутри метода register вызовом
RegisterPropertyEditor(TypeInfo(<тип свойства>), <тип компонента>, <имя свойства>, <тип редактора свойства>);
RegisterPropertyEditor(TypeInfo(TMyComponentsStringProperty), TMyComponent, '', TMCSPEditor);
Передав вместо имени свойства пустую строку, мы указали тем самым, что имя может быть любым. Так же пустую строку можно передать вместо имени компонента.
Вот, собственно, и все. Пишите свой редактор свойств, переопределяйте нужные методы и вперед!
Delphi 7 ToolsAPI: Эксперты
Написание простейшего эксперта
Какой же код нужно написать для создания простейшего эксперта? Для этого нужно написать класс, унаследованный от IOTAWizard (определен в файле ToolsAPI.pas) или одного из его потомков, расположить в модуле процедуру Register, как мы это делали с компонентами, и вызвать внутри ее процедуру
RegisterPackageWizard (const Wizard: IOTAWizard);
например:
RegisterPackageWizard (TMyExpert.Create as IOTAWizard);
передав ей в качестве параметра экземпляр заранее созданного эксперта.
Рассмотрим класс IOTAWizard.
IOTAWizard = interface(IOTANotifier)
['{B75C0CE0-EEA6-11D1-9504-00608CCBF153}']
{ Expert UI strings }
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
{ Launch the AddIn }
procedure Execute;
end;
Интерфейс IOTANotifier нам не понадобится, поэтому давайте рассмотрим методы IOTAWizard: Метод GetIDString должен возвращать уникальный идентификатор эксперта. Например: MyCompany.MyExpert
Метод GetName должен возвращать название эксперта
Метод GetState должен возвращать [wsEnabled], если эксперт функционирует, wsChecked если выбран.
Метод Execute вызывается при запуске эксперта из среды IDE.
Итак, если вы хотите сами программировать действия вашего эксперта, включая добавление в меню IDE и прочее и прочее, унаследуйте его от IOTAWizard.
Если вы хотите, чтобы ваш эксперт отображался в репозитории Delphi на произвольной странице и по щелчку по его иконке вызывался его метод Execute - унаследуйте его от IOTARepositoryWizard
IOTARepositoryWizard = interface(IOTAWizard)
['{B75C0CE1-EEA6-11D1-9504-00608CCBF153}']
function GetAuthor: string;
function GetComment: string;
function GetPage: string;
function GetGlyph: Cardinal;
end;
Метод GetAuthor должен возвращать имя автора,
Метод GetComment - комментарий,
Метод GetPage - страницу на которой будет расположена иконка эксперта
Метод GetGlyph - дескриптор иконки
Если вы хотите, чтобы эксперт появлялся на странице форм в репозитарии - унаследуйте его от IOTAFormWizard. Он имеет все те же методы и свойства, что и IOTARepositoryWizard, если на странице проектов - от IOTAProjectWizard. Он тоже аналогичен IOTARepositoryWizard.
Если же вы хотите, чтобы пункт меню для вызова метода вашего эксперта Execute помещался в мень Help главного меню IDE, унаследуйте вашего эксперта от IOTAMenuWizard:
IOTAMenuWizard = interface(IOTAWizard)
['{B75C0CE2-EEA6-11D1-9504-00608CCBF153}']
function GetMenuText: string;
end;
Метод GetMenuText должен возвращать имя пункта меню для отображения, а метод GetState возвращает стиль элемента меню (Enabled, Checked)
Вот так все просто, оказывается!
Расположение эксперта внутри DLL библиотеки
Если вы хотите расположить вашего эксперта не в пакете, а в DLL библиотеке, библиотека должна экспортировать функцию INITWIZARD0001 следующего формата:
type TWizardRegisterProc = function(const Wizard: IOTAWizard): Boolean;
type TWizardTerminateProc = procedure;
function INITWIZARD0001(const BorlandIDEServices: IBorlandIDEServices; RegisterProc: TWizardRegisterProc; var Terminate: TWizardTerminateProc): Boolean stdcall;
Для регистрации вашего эксперта вызовите внутри этой функции RegisterProc и передайте ей экземпляр заранее созданного класса вашего эксперта. BorlandIDEServices - указатель на основной интерфейс для работы со всей IDE. Отдельные части его мы рассмотрим далее. По окончании работы IDE или при принудительной выгрузке вашего эксперта будет вызвана функция Terminate, которую вы должны передать среде.
Поместите полный путь к DLL в ключ реестра
HKEY_CURRENT_USER\Software\Borland\Delphi\7.0\Experts
или
HKEY_LOCAL_MACHINE\SOFTWARE\Borland\Delphi\7.0\Experts
Именем ключа может быть произвольная строка.
Эксперт будет запущен только при перезапуске среды, если она выполнялась. Вуаля!
Автор статьи: Раструсный Владислав Юрьевич
Взято с сайта
Delphi и 1C - экспорт и импорт
Delphi и 1C - экспорт и импорт
Автор: Александр Авдошин
Специально для Королевства Delphi
Довольно часто перед программистами, работающими в небольших компаниях, стоит проблема импорта данных из программы "1С:Предприятие", или экспорта в нее же. Причин тому может быть множество - например, желание автоматизировать обновление прайс-листа на веб-страничке компании на основании реальных данных, или же автоматизация ввода первичных документов, отправляемых по электронной почте компанией-поставщиком. Какая бы задача подобного рода ни стояла перед программистом, она, как правило, успешно решается с помощью связки Delphi-1C. В этой статье я хотел бы дать рекомендации и разъяснить некоторые аспекты использования механизма OLE Automation применительно к программе "1С:Предприятие версия 7.7".
Перед прочтением статьи я настоятельно рекомендую Вам ознакомиться с книгой "Delphi 4 Unleashed" Чарльза Калверта и с главой "Связь с внешними приложениями посредством механизмов DDE и OLE Automation" книги "1С:Предприятие 7.7 Описание встроенного языка". Также я предполагаю, что вы имеете опыт программирования как в среде Delphi, так и в среде "1С:Предприятие".
Первые шаги
Ну, во-первых, прежде чем использовать все возможности программы "1С:Предприятие", необходимо сначала создать соответствующий OLE-объект. Идентификатор этого OLE-объекта зависит от версии и типа установленной программы "1С:Предприятие":
V1CEnterprise.Application - версия независимый ключ
V77.Application - версия зависимый ключ
V77S.Application - версия зависимый ключ, SQL-версия
V77L.Application - версия зависимый ключ, локальная версия
V77M.Application - версия зависимый ключ, сетевая версия
Например, создадим OLE-объект для сервера "1С:Предприятие". Для простоты создадим объект без привязки к конкретной версии и типу программы:
procedureTForm1.Create1C;
var
onesobj: Olevariant;
begin
onesobj := createoleobject('V1CEnterprise.Application');
end;
Затем мы должны проинициализировать систему методом Initialize, имеющим следующие параметры:
Initialize(<Имя_Объекта>.RMTrade,<КоманднаяСтрока>,<ПустаяСтрока>), где:
<Имя_Объекта> - Идентификатор созданного OLE объекта
<КоманднаяСтрока> - Строковое выражение - командная строка запуска
<ПустаяСтрока> - Строковое выражение. Может содержать пустую строку или строковое значение "NO_SPLASH_SHOW" - отключить заставку при запуске системы.
Метод Initialize возвратит значение логического типа: TRUE, если инициализация прошла удачно, или FALSE в противном случае. Следует иметь в виду, что в OLE Automation TRUE и FALSE имеют соответственно значения -1 (минус единица) и 0.
Параметры командной строки запуска подробно описаны в руководстве к программе "1С:Предприятие", здесь же я приведу лишь те, которые могут оказаться вам полезными:
/DПуть к базе - задает путь к базе программы.
/M - запуск программы в монопольном режиме
/NИмя пользователя
/PПароль - пароль указанного пользователя
Параметры, не указанные в командной строке, будут запрошены программой в диалоговом режиме.
Например, инициализация программы в монопольном режиме с явным указанием пути к базе данных (D:\buh2001test), имени пользователя (Саша) и пароля (12345) без вывода на экран заставки выполняется следующим образом (здесь и далее подразумевается, что объект onesobj уже создан оператором createoleobject):
onesobj.initialize(onesobj.rmtrade,'/DD:\buh2001test /M /NСаша /P12345','NO_SPLASH_SHOW');
В отличие от, например, OLE Automation-сервера приложения Microsoft Excel, сервер программы "1С-Предприятие" запускается в режиме "hide", то есть рабочее окно программы не отображается на экране.
Для использования созданного и проинициализированного объекта необходимо просто обращаться к атрибутам и методам системы 1С:Предприятие как OLE Automation сервера.
Для завершения работы с программой необходимо освободить OLE-объект путем присвоения ему значения UnAssigned:
onesobj := UnAssigned;
Впрочем, это необязательно, так как при закрытии вашего приложения OLE-объект будет освобожден автоматически.
Просуммируем полученные знания: создадим OLE-объект "1С:Предприятие", проинициализируем его и корректно освободим:
procedure TForm1.Create1C;
var
onesobj: Olevariant;
begin
onesobj := createoleobject('V1CEnterprise.Application');
onesobj.initialize(onesobj.rmtrade,
'/DD:\buh2001test /M /NСаша /P12345', 'NO_SPLASH_SHOW');
onesobj := UnAssigned;
end;
Как работать с полученным объектом
Резонный вопрос. Собственно, ради этого все и затевалось, не так ли? :) На самом деле, все очень просто. После того, как мы создали и проинициализировали OLE-объект, работать с ним можно следующим образом:
С помощью метода EvalExpr(<СтрокаВыражения>)
Метод EvalExpr вычисляет выражение, записанное параметре <СтрокаВыражения> на встроенном языке 1С:Предприятие и возвращает результат вычисления. Результатом выражения может быть число, строка, дата или значение любого агрегатного типа данных.
С помощью метода CreateObject(<ИмяАгрегатногоТипа>)
Метод CreateObject создает объект агрегатного типа данных системы 1С:Предприятие и возвращает ссылку на него. Данная функция обычно используется одновременно с явным определением переменной типа OLEVariant и присвоением ей ссылки на объект агрегатного типа данных.
С помощью метода ExecuteBatch(<СтрокаОператоров>)
Метод ExecuteBatch выполняет последовательность операторов, записанную в параметре <СтрокаОператоров> на встроенном языке 1С:Предприятие. Метод возвращает -1, если последовательность операторов выполнена успешно, или 0 в противном случае.
Вызовом атрибутов и методов системы 1С:Предприятие как OLE Automation сервера
Здесь есть несколько подводных камней, о которых я сразу хочу предупредить:
При вызове атрибутов и методов системы 1С:Предприятие необходимо использовать их англоязычные синонимы (они указаны для каждого метода в книге "Описание встроенного языка")
Для создаваемого агрегатного типа данных в среде Delphi необходимо завести переменную типа OLEVariant
В случае, если вызываемый метод OLE-объекта не требует параметров (либо один из параметров является необязательным), в качестве параметра ему необходимо передавать EmptyParam (либо - для Delphi 3 - пустую строку).
Для обращения к русскоязычным идентификаторам объектов агрегатных типов (например, реквизитов справочников) следует использовать метод объекта агрегатного типа getattrib(<ИмяАтрибута>) для получения значения атрибута, и setattrib(<ИмяАтрибута>) для установки значения.
Для комплексной иллюстрации всего вышеописанного я приведу пример, в котором содержимое справочника "Номенклатура" целиком экспортируется в таблицу базы данных (в примере подразумевается, что уже создана таблица table1, поля которой адекватны справочнику. Таблица table2 ссылается на ту же физическую таблицу, что и table1, и служит лишь для поиска уже добавленных элементов):
procedure TForm1.exportsprav;
var
counter: integer; //Счетчик импортированных записей
onesobj: Olevariant; //OLE-объект программы 1С:Предприятие
ware, ware2: olevariant; //Агрегатные объекты
val, edizm, nds, np: olevariant;
pf: integer; //Промежуточные переменные
begin
table1.open; //Открываем таблицу1
table2.open; //Открываем таблицу2
counter := 0; //Обнуляем счетчик записей
onesobj := createoleobject('V1CEnterprise.Application'); //Создаем OLE-объект
//Инициализируем объект
onesobj.initialize(onesobj.rmtrade, '/DD:\buh2001test /M /NСаша /P12345', 'NO_SPLASH_SHOW');
//Создаем необходимые агрегатные объекты
ware := onesobj.createobject('Справочник.Номенклатура');
ware2 := onesobj.createobject('Справочник.Номенклатура');
edizm := onesobj.createobject('Справочник.ЕдиницыИзмерений');
nds := onesobj.createobject('Справочник.СтавкиНДС');
np := onesobj.createobject('Справочник.СтавкиНП');
ware.selectgroup(1); //Устанавливаем режим выборки групп
ware.selectitems(1); //Открываем выборку элементов справочника
while ware.GetItem(1) > 0 do //Выбираем все элементы
begin
if ware.level('') = 1 then //Если мы выбрали группу первого уровня, то
pf := -1
else
begin
//Иначе ищем элемент-родитель
ware2.FindItem(ware.getattrib('Родитель'));
if table2.findkey([ware2.getattrib('Код')]) then
//Если этот элемент мы уже импортировали
pf := table2.fieldbyname('ID').AsInteger //, то получаем его код
else
pf := -1; //иначе помещаем элемент в группу первого уровня
end;
if ware.deletemark('') = 0 then //Если элемент не удален, то
begin
table1.append; //добавляем новое поле к таблице
//Заполняем поля таблицы значениями соответствующих атрибутов элемента справочника
table1.fieldbyname('CODE_1S').AsInteger := ware.getAttrib('Код');
//Заполняем поле наименования
table1.fieldbyname('NAME').AsString := ware.getAttrib('Наименование');
table1.fieldbyname('PARENT_FOLDER').AsInteger := pf;
table1.fieldbyname('FULLNAME').AsString := ware.getAttrib('ПолнНаименование');
//Ищем соответствующую запись в справочнике "единицы измерения"
edizm.finditem(ware.getattrib('ЕдиницаИзмерения'));
//Заполняем поле единицы измерения
table1.fieldbyname('EDIZM').AsString := edizm.getattrib('Наименование');
//так мы получаем значения периодических реквизитов
table1.fieldbyname('SEBESTOIM').AsFloat :=
ware.getAttrib('Себестоимость').GetValue(datetostr(now));
table1.fieldbyname('PRICEOPT').AsFloat := ware.getAttrib('Цена');
nds.finditem(ware.getAttrib('СтавкаНДС').GetValue(datetostr(now)));
np.finditem(ware.getAttrib('СтавкаНП').GetValue(datetostr(now)));
//Заполняем поле ставки НДС
table1.fieldbyname('STNDS').AsFloat := nds.getAttrib('Ставка');
//Заполняем поле ставки НП
table1.fieldbyname('STNP').AsFloat := np.getAttrib('Ставка');
table1.fieldbyname('ARTICUL').AsString := ware.getAttrib('Артикул');
if Ware.IsGroup('') = 1 then //Если мы выбрали группу товара, то
table1.fieldbyname('IS_FOLDER').AsInteger := 1
else
table1.fieldbyname('IS_FOLDER').AsInteger := 0;
table1.post;
table2.refresh;
end;
inc(counter);
end;
end;
Заключение
К сожалению, невозможно вместить в одну статью всю информацию, которая была бы вам полезна. Я постарался дать лишь тот минимум, который необходим для получения некоторых базовых знаний, и способен стать фундаментом для ваших собственных маленьких открытий в области интеграции Delphi и "1С:Предприятие".
Пишите мне, задавайте вопросы, и вполне возможно, что вскоре появится продолжение статьи.
Взято с
Delphi и CGI
Delphi и CGI
В последнее время в связи с растущей популярностью сети Интернет все чаше становится необходимость разработки приложений, которые бы могли работать непосредственно в www среде. Т.е. такие, которые бы полностью бы интегрировались в уже привычные нам веб-странички. По сути дела работа с таким приложением происходит полностью через любимый браузер пользователя и ничем не отличается от серфинга по страничкам. Ввод данных равно как и выдача обработанных результатов происходит через html-формы веб-страничек. Обработка же данных происходит на веб-сервере. Таким образом, мы получим самое что ни есть клиент-серверное приложение в его самом классическом понимании.
Необходимо отметить, что CGI-приложения разрабатываемые в средах разработки ориентированных на Win32 системы, в том числе и в Дельфи, а вернее серверная часть такого приложения может работать только под Win32 сервером, например IIS из NT или Personal Web Server из Windows98. Что касается клиентской части, то здесь никаких проблем совместимости не должно быть в принципе, т.к. клиентская часть представляет собой сгенерированный HTML код, который поддерживается любым браузером, не важно какую платформу использует пользователь, будь то Win32, OS/2, Unix и др.
Таким образом, программисту пишушему CGI-приложения придется столкнуться с двумя основными задачами - это разработка веб-интерфейса и разработка непосредственно математической части приложения.
Что касается веб-интерфейсов, то здесь желательно знать хотя бы основы языка HTML. Здесь мы не будем уделять этому особое внимание, хотя знание HTML для программиста CGI-приложений очень желательно. Сейчас же для нас будет вполне достаточным знание таких основопологающих тэгов как <HTML>,<BODY> и конструкции <FORM>.
Ну а теперь будем разбираться непосредственно с телом CGI-приложения. Во-первых, что такое CGI-приложение разрабатываемое в Win32 среде разработки? Это приложение типа Win32 CONSOLE, т.е. консольное приложение Win32. Только вот для такого приложения в отличии от классической Win32 консоли стандартным устройством ввода является либо поля ввода HTML формы либо строка адреса браузера, а в качестве стандартного устройства вывода используется окно браузера. Активизация приложения происходит непосредственно из какой-либо HTML странички, например так <A HREF="http://myhost/myapp.exe">My Application</A> Как мы уже выяснили такое CGI-приложение будет представлять собой исполняемую Win32 программу (exe), таким веб-приложениям принято давать расширение CGI, хотя это и непринципиально.
Для начала рассмотрим пример самой простой CGI-программки выдающей в окно пользовательского браузера текст "HELLO WORLD".
program MyApp
{$APPTYPE CONSOLE} // тип приложения Win32 консоль
{$E cgi} // Расширение приложения cgi
begin
WriteLn('Content-Type: text/html');
WriteLn;
WriteLn;
WriteLn('<HTML>');
WriteLn('<HEAD>');
WriteLn('<TITLE>Простейшее CGI приложение</TITLE>');
WriteLn('<META http-equiv="Content-Type" content="text/html;' +
' charset=windows-1251">');
WriteLn('</HEAD>');
WriteLn('<BODY>');
WrОтiteLn('<H1>HELLO WORLD</H1>');
WriteLn('</BODY>');
WriteLn('</HTML>');
end.
Откомпиллируйте этот исходный код в среде Дельфи, поместите скомпонованный исполняемый код в ваш CGI-BIN каталог ( в каталог, где разрешено исполнение скриптов), напишите небольшую веб страничку для активизации нашего CGI-приложения, например, такую:
<HTML>
<HEAD>
<TITLE>Форма для активизации CGI-приложения</TITLE>
</HEAD>
<BODY>
<A HREF="http://localhost/cgi-bin/myapp.cgi">
Нажми сюда для запуска приложения</A>
</BODY>
</HTML>
Теперь откройте нашу веб-страничку и перейдите по ссылке "Нажми сюда для запуска приложения". Если вы все сделали правильно, то на экране в окне вашего любимого браузера появиться текст "HELLO WORLD".
Как видите все достаточно просто! Однако, для полноценной работы приложения, оно должно уметь не только выводить некие данные, но получать данные от пользователя, т.е. обеспечивать ввод информации. Ввод данных в случае CGI-приложения, как мы уже говорили, будет осуществляться по средствам интерфейса организованного веб-формой. Такая форма может передавать данные двумя способами, в зависимости от значения атрибута "METHOD".
В случае <FORM METHOD="GET" ...>... данные передаются через строку адреса браузера и записываются в переменную системного окружения QUERY_STRING, а размер строки данных в переменную CONTENT_LENGTH.
В случае <FORM METHOD="POST" ...>... передаваемые данные в строке адреса не отображаются, передаются через стандартный поток ввода консольной программы.
Таким образом задача получения данных CGI-приложением сводится к чтению определенной переменной окружения. Надо отметить, что передаваемые веб-формой данные имеют следующий формат: <имя_атрибута1>=<значение_атрибута1>&<имя_атрибута2>=<значение_атрибута2>...
Задача программиста сводится к извлечению значений нужных атрибутов из полученной от браузера строки и преобразования этих значений из вида URLencoded в обычные текстовые данные. Суть URLencoded формата заключается в том, что некоторые символы, содержащиеся в значении поля, заменяются на % и следующим за ним шестнадцатиричным кодом символа, а пробел заменяется на +.
А сейчас давайте рассмотрим пример CGI приложения, которое бы производило подобие некоторой идентификации пользователя системы.
<!-- HTML форма ввода пароля -->
<HTML>
<HEAD>
<TITLE>Авторизация доступа</TITLE>
</HEAD>
<BODY>
<FORM method="POST" action="http://localhost/cgi-bin/chkpaswd.cgi">
Введите пароль:
<input type="text" name="paswd" size=20>
<input type="submit" value="Найти">
<input type="reset" value="Очистить">
</FORM>
</BODY>
</HTML>
Далее идет пример непосредственно CGI приложения. Следует отметить, что приведенный в этом примере способ получения данных от веб формы (непосредственное чтение устройства стандартного ввода STD_INPUT) является наиболее наглядным, но не самым удобным, в Дельфи предусмотренны более удобные механизмы, которых мы каснемся позже.
{Файл проекта CGIApp2}
program CGIApp2;
{$APPTYPE CONSOLE}
uses
MainUn in 'MAinUn.pas';
{$E cgi}
begin
Main;
end.
program MainUn;
interface
uses
SysUtils, Windows, Classes;
implementation
// Функция перевода шестнадцетиричного символа в число
function HexToInt(CH : char): integer;
begin
Result:=0;
case CH of
'0'..'9': Result:=Ord(CH)-Ord('0');
'A'..'F': Result:=Ord(CH)-Ord('A')+10;
'a'..'f': Result:=Ord(CH)-Ord('a')+10;
end;
end;
// Преобразует символы, записанные в виде URLencoded
function Decode(Value: string): string;
var
i, L: integer;
begin
Result:='';
L:=0;
for i := 1 to Length(Value) do
begin
if(Value[i] <> '%') and (Value[i] <> '+') and (L<1) then
begin
Result := Result + Value[i];
end
else
begin
if(Value[i] = '+') then
Result := Result + ' '
else if(Value[i] = '%') then
begin
L := 2;
if(i < Length(Value) - 1) then
begin
Result := Result + Chr(HexToInt(Value[i+1]) * 16 +
HexToInt(Value[i+2]));
end;
end
else
Dec(L);
end;
end;
end;
// Фнкция возвращает значение атрибута заданного
//в качестве параметра функции из строки данных
//считанной из устройства стандартого ввода.
function ParamByName(Name: string): string;
var
SS, ST : string;
K : integer;
begin
Result := '';
SS := InParams;
while Length(SS) <> 0 do
begin
K := Pos('&',SS);
if (K <> 0) then
begin
ST := Copy(SS,1,K-1);
SS := Copy(SS,K+1,10000);
end
else
begin
ST :=SS;
SS:='';
end;
K := Pos('=',ST);
if(K <> 0) then
begin
if(Name = Copy(ST,1,K-1)) then
begin
Result := Decode(Copy(ST,K+1,6000));
end;
end;
end;
end;
procedure Main;
var
STR : string;
StdIn, Size, Actual : cardinal;
InParams : string;
const
UserPassword : String = 'MyPass';
begin
StdIn := GetStdHandle(STD_INPUT_HANDLE);
Size := SetFilePointer(StdIn, 0, nil, FILE_END);
SetFilePointer(StdIn, 0, nil, FILE_BEGIN);
SetLength(STR,Size+1);
if (Size <= 0) then
Exit;
// Читаем данные из стандартного устройства ввода
ReadFile(StdIn, STR[1], Size, Actual, nil);
STR[Size+1] := #0;
InParams := PChar(@STR[1]);
APasswd := ParamByName('paswd');
WriteLn('Content-Type: text/html');
WriteLn;
WriteLn;
WriteLn('<HTML>');
WriteLn('<HEAD>');
WriteLn('<TITLE>Идентификация пользователя</TITLE>');
WriteLn('<META http-equiv="Content-Type" content="text/html;'+
' charset=windows-1251">');
WriteLn('</HEAD>');
WriteLn('<BODY>');
if APasswd = UserPassword then
WriteLn('<H1>Успешная идентификация!</H1>')
else
WriteLn('<H1>Пароль введен неверно!</H1>')
WriteLn('</BODY>');
WriteLn('</HTML>');
end;
Ну, вот пока что все. Но это совсем не значит, что мы полностью охватили всё, что касается разработки CGI приложений на Дельфи. В самое ближайшее время я обязательно продолжу это повествование, ведь впереди еще столько интересной и полезной информации. Так что, до скорого!
Взято с Исходников.ru
Delphi и Corel Draw.
Delphi и Corel Draw.
Надеюсь, многие сталкивались с Corel Draw? А у многих слюнки текли, что это мощнейший графический редактор и хотелось бы под него свои программы писать, к примеру, чертежи выводить? Я один из вас :)
Формат файлов *.cdr конечно, не представлю, т.к. сам его не знаю :), но как с этим зверем работать расскажу. Вычитал, что с Corel Draw можно работать только через скрипт, причем изначально я готовил файлы скриптов *.csc, а затем их запускал в самом редакторе. Рабочий инструмент для освоения - Corel Script Editor. Если Вы хотите действительно что-то написать, то он вам просто необходим, хотя бы ради того, что смотреть как Corel Draw их сам создает, ну и самое главное - дока по языку и функциям. Все замечательно, только вот скрипты медленно работают т.к. они эмитируют работу человека - т.е. кнопочки сами нажимаются, панельки меняются и т.д.
А чертеж, к примеру на котором около 3000 объектов мог загружаться и исполнятся до часу! Нет, когда это утомляет, то можно и самому посидеть - глядишь за неделю сделаешь :)
И тут я "чисто случайно" наткнулся на статейку http://www.djpate.freeserve.co.uk/AutoCDrw.htm. Оказывается можно и через OLE этот Corel Draw дергать, и как оказывается, не так уж оно и сложно. Да, совершенно верно, нужно использовать CorelDraw.Automation.xx. Я возился с 8-й версией. Забегая на перед, скажу, что тот же чертеж выводился в течении 5-10 минут.
Ну что, начнем?
var
CorelDraw: Variant;
…
CorelDraw := CreateOleObject('CorelDraw.Automation.8');
// цифирку можете свою поставить
CorelDraw.FileNew;
// или CorelDraw.FileOpen(FileName);
CorelDraw.SetDocVisible(True);
// можно и не показывать, что он там делает, но ведь интересно! :)
// кстати, можно нарисовать, а потом показать - будет на 30% быстрее
... // ну и в конце
CorelDraw.FileSave('NewName', 0, False, 0, False);
CorelDraw.FileExit(False); // можно не писать, если не надо закрывать
CorelDraw := Unassigned;
Формат функций доступным английским языком описан в draw_scr.hlp. Ну а дальше, чего душа (или начальство :) ) желает:
CorelDraw.SetPageOrientation(0);
CorelDraw.SetPageSize(PageW, PageH);
CorelDraw.NewLayer('NewLayer1');
CorelDraw.SelectLayer('NewLayer1');
CorelDraw.CreateEllipse(CalcY(Y1)), CalcX(X1), CalcY(Y2), CalcX(X2), 0, 0, 0);
// ничего я не перепутал!!! именно так у них координаты!
CorelDraw.CreateRectangle(CalcY(Y1)), CalcX(X1), CalcY(Y2), CalcX(X2), CalcX(Radius));
...
Все ясно? За дело!
Да, чуть не забыл о самом главном - как и у любой системы в Corel Draw есть свои "заморочки" :)
Ноль координат находится в середине листа бумаги (оригинально, правда?)
Положительная ось Y направлено вверх, а X - в право.
Координаты - целые числа в микронах. Для удобства я писал функцию:
function CalcX(x_mm:double):longint;
begin
result := Round(x_mm*10000);
end;
Углы не знаю в чем, но 90 градусов надо записать как 90000000. Положительные против часовой стрелки.
Принцип прорисовки таков: создается объект, а затем ему присваиваются различные свойства, такие как цвет, тип заливки, толщина линий и пр. По умолчанию эти значения через автоматизацию выставить нельзя - не поддерживается.
Выше упомянутое наглядно видно на прорисовке текста:
CorelDraw.CreateArtisticText( Text, CalcX(X), CalcY(Y));
// создаем текст. X,Y - левый нижний
// как видите, нет параметров шрифта, размера и пр.
With Font do
begin
if (Italic) and (Bold) then FSK:=14 else
if (Italic) then FSK:=8 else
if (bold) then FSK:=13 else FSK:=7;
end;
CorelDraw.SetCharacterAttributes( 0, 0, Font.Name, FSK, Abs(Font.Size)*10,
0, 0, 0, 0, 0, 1000, 1000, HAlign);
// присваиваем атрибуты шрифта.
// HAlign имеет значения 1,2,3 - влево, по центру, вправо соответственно
ColorToCMYK(Font.Color, C,M,Y,K);
// это моя функция для преобразования tColor в составляющие в модели CMYK
CorelDraw.StoreColor(2, C,M,Y,K, 0,0,0,0); // создание цвета
CorelDraw.ApplyUniformFillColor; // применяем цвет к объекту
Тоже самое относится к трансформации объектов ? сперва создаете, а затем изменяете как хотите.
Работают функции для получения информации.
CorelDraw.GetSize(XSize, YSize); // получили размеры объекта
CorelDraw.MoveObject(0, -YSize); // сдвинули его вниз на свой размер
Можно "проверить" все существующие объекты. За круглым столом спрашивали, как это делается, а делается это так:
var ObjID, FirstObjID:longint;
begin
CorelDraw.SelectAllObjects;
CorelDraw.SelectNextObject(true);
// true для "захода" в сгруппированный объект
FirstObjID := CorelDraw.GetObjectsCDRStaticID;
Repeat
...
// работа с объектом CorelDraw.SelectNextObject(true);
ObjID := CorelDraw.GetObjectsCDRStaticID;
Until ObjID = FirstObjID; ...
Взято с Исходников.ru
Delphi и Flash
Delphi и Flash
Delphi и Flash. Совмещение несовместимого!
Разве возможно совместить Флэш-ролики и Дельфи-приложения. Раньше я думал что НЕТ. Но теперь я знаю не только, что это возможно, но и знаю как это делается!!! И сейчас я вам расскажу об этом. Во-первых хочется отметить преимущества использования флэш-роликов в ваших программах. Если вы сумеете гармонично вписать небольшой флэш-ролик в вашу программу, то несомненно внешний вид программы будет намного привлекательнее (главное не переборщить, увлекаясь дизайном, не надо забывать о том что программа должна быть удобна и проста в использовании! ).
Итак, как же совместить Флэш и Дельфи? (Надеюсь, что у вас Флэш установлен:))
Запустите Дельфи и выберите пункт меню Component->Import ActiveX Control... Перед вами откроется диалоговое окно с заголовком Import ActiveX Control. В разделе Registered Controls выберите Shockwave Flash. В разделе Pallete Page... Выберите страницу в палитре компонентов, на которой будет располагаться установленный компонент (по умолчанию это ActiveX). В разделе Unit Dir Name... путь к папке куда будет установлен компонент.
Нажмите на кнопку Install. Перед вами появится окно, в котором вам нужно будет выбрать в какой пакет будет установлен компонент (вы можете установить как в уже существующий, так и в новый пакет). Затем перед вами появится окно редактирования выбранного пакета и Дельфи вас спросит: "...Package will be rebuilt. Continue?". Ответьте Yes. Все готово теперь можно использовать флэш в ваших приложениях!!!
Теперь, чтобы показать вам как пользоваться этим компонентом, попробуем вместе сделать программу для просмотра *.SWF файлов. Для этого нам понадобятся следующие компоненты: TShockwaveFlash (для удобства назовите его просто Flash1), TTrackBar, TTimer, TOpendialog и три кнопки TButton ("открыть", "старт" и "стоп").
Для начала установим необходимые свойства OpenDialog'a
Свойство Filter может быть таким: Флэш-ролики|*.swf
Свойство DefaultExt должно быть: *.swf
Для Timer'a нужно установить свойство Interval равным 1.
Для TShockwaveFlash:
Name сделайте равным Flash1
Свойство Playing установите в false
Свойство BGColor, установите как вам хочется (цвет фона)
Теперь напишем обработчик события OnClick для кнопки, которая вызывать OpenDialog:
if open1.Execute then begin
flash1.Movie:=open1.FileName;
trackbar1.Max:=flash1.TotalFrames; {это делается для того, чтобы потом можно было перемещаю ползунок посмотреть каждый кадр ролика}
В обработчик события OnClick для второй кнопки ("Старт") напишем:
flash1.Play;
Ну тут вообще все просто! Почти таким же образом это будет выглядеть для третьей кнопки ("Стоп"):
flash1.Stop;
Теперь сделаем, чтобы при перемещении ползунка Trackbar'a мы могли посмотреть каждый кадр (событие OnChange):
if Flash1.IsPlaying=true then Flash1.Stop; {если ролик проигрывается, то надо его остановить}
flash1.GotoFrame(trackbar1.position); {открываем кадр номер которого соответствует позиции ползунка}
Ну и наконец осталось сделать чтобы при проигрывании ролика ползунок перемещался, указывая сколько осталось и сколько прошло. Для этого то мы и используем Timer. В обработчик события OnTimer,напишем:
trackbar1.Position:=flash1.CurrentFrame;
Приведу полный код приложения:
unit flash;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, StdCtrls, OleCtrls, ShockwaveFlashObjects_TLB, ExtCtrls;
type
TForm1 = class(TForm)
Flash1: TShockwaveFlash;
Button1: TButton;
TrackBar1: TTrackBar;
Open1: TOpenDialog;
Button2: TButton;
Button3: TButton;
Timer1: TTimer;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure TrackBar1Change(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
if open1.Execute then
begin
flash1.Movie := open1.FileName;
trackbar1.Max := flash1.TotalFrames;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
flash1.Play;
end;
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
if Flash1.IsPlaying = true then Flash1.Stop;
flash1.GotoFrame(trackbar1.position);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
flash1.Stop;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
trackbar1.Position := flash1.CurrentFrame;
end;
end.
Ну вот и все. Как оказалось ничего сложного.
Дополнительная информация
Автор: Михаил Христосенко.
Если у вас возникнут какие-нибудь вопросы, предложения и пожелания, а также ваши отзывы шлите по почте: kikoz@kemtel.ru
Заходите на мой сайт http://MihanDelphi.narod.ru там вы найдете множество программ (моих и не только), компонентов, статей и еще множество полезностей для Дельфера.
Взято с сайта
Delphi IDE, компиллятор, отладчик, редактор
Delphi IDE, компиллятор, отладчик, редактор
Cодержание раздела:
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
Delphi Run-Time Error Codes
Delphi Run-Time Error Codes
Delphi's online help documentation seems to miss the run-time error codes. I usually looked them up in my old Borland Pascal for Windows help file - now they are here for fast access:
1 Invalid function number
2 File not found
3 Path not found
4 Too many open files
5 File access denied
6 Invalid file handle
12 Invalid file access code
15 Invalid drive number
16 Cannot remove current directory
17 Cannot rename across drives
100 Disk read error
101 Disk write error
102 File not assigned
103 File not open
104 File not open for input
105 File not open for output
106 Invalid numeric format
200 Division by zero
201 Range check error
202 Stack overflow error
203 Heap overflow error
204 Invalid pointer operation
205 Floating point overflow
206 Floating point underflow
207 Invalid floating point operation
210 Object not initialized
211 Call to abstract method
212 Stream registration error
213 Collection index out of range
214 Collection overflow error
215 Arithmetic overflow error
216 General protection fault
Взято с сайта
Демонстрация DefineProperties
Демонстрация DefineProperties
Хорошо, создайте на основе опубликованного ниже кода модуль PropDemo.pas и добавьте новый компонент в палитру компонентов. Расположите его на форме и сохраните ее. Затем посмотрите файл DFM каким-либо шестнадцатиричным редактором и проверьте наличие определенных свойств по их именованным тэгам. Вы можете также попробовать закрыть форму и модуль, а затем открыть его с помощью пункта меню File | Open file..., изменив тип файла в выпадающем списке на *.DFM.
Mike Scott
Mobius Ltd.
unitPropDemo;
{ Демонстрация DefineProperties.Mike Scott, CIS 100140,2420. }
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs;
type
TDemoProps = class(TComponent)
private
{ Private declarations }
FStringThing: string;
FThing: record
i, j, k: integer;
x, y: real;
ch: char;
end;
procedure ReadStringThing(Reader: TReader);
procedure WriteStringThing(Writer: TWriter);
procedure ReadThing(Stream: TStream);
procedure WriteThing(Stream: TStream);
protected
{ Protected declarations }
procedure DefineProperties(Filer: TFiler); override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
{ Published declarations }
end;
procedure Register;
implementation
constructor TDemoProps.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
{ создайте любые данные, чтобы было что передать в поток}
FStringThing := 'Всем привет!';
with FThing do
begin
i := 1;
j := 2;
k := 3;
x := PI;
y := 180 / PI;
ch := '?';
end;
end;
procedure TDemoProps.ReadStringThing(Reader: TReader);
begin
FStringThing := Reader.ReadString;
end;
procedure TDemoProps.WriteStringThing(Writer: TWriter);
begin
Writer.WriteString(FStringThing);
end;
procedure TDemoProps.ReadThing(Stream: TStream);
begin
Stream.ReadBuffer(FThing, sizeof(FThing));
end;
procedure TDemoProps.WriteThing(Stream: TStream);
begin
Stream.WriteBuffer(FThing, sizeof(FThing));
end;
procedure TDemoProps.DefineProperties(Filer: TFiler);
begin
inherited DefineProperties(Filer);
Filer.DefineProperty('StringThing', ReadStringThing, WriteStringThing,
FStringThing <> '');
Filer.DefineBinaryProperty('Thing', ReadThing, WriteThing, true);
end;
procedure Register;
begin
RegisterComponents('Samples', [TDemoProps]);
end;
end.
Взято из
Советов по Delphi от
Сборник Kuliba
Демонстрация обратного вызова BDE
Демонстрация обратного вызова BDE
Существует обратный вызов (callback) BDE, который вы можете использовать для получения уведомлений об изменении таблиц Paradox. Тем не менее от вас все же потребуется использование таймера. Функция обратного вызова инициируется при вызове функций, осуществляющих доступ к таблице. Ниже приведен код, демонстрирующий технику работы с описанным выше обратным вызовом:
TCMAIN.PAS:
unittcmain;
{ Демонстрация cbTableChange }
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DB, DBTables, ExtCtrls, DBCtrls, Grids, DBGrids, BDE, StdCtrls;
const
WM_UPDATETABLE = WM_USER + 1;
type
TForm1 = class(TForm)
Table1: TTable;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
Timer1: TTimer;
Button1: TButton;
procedure Table1AfterOpen(DataSet: TDataSet);
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
FChgCnt: Integer;
FCB: TBDECallback;
function TableChangeCallBack(CBInfo: Pointer): CBRType;
procedure UpdateTableData(var Msg: TMessage); message WM_UPDATETABLE;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
// Это функция, вызываемая функцией обратного вызова.
function TForm1.TableChangeCallBack(CBInfo: Pointer): CBRType;
begin
Inc(FChgCnt);
Caption := IntToStr(FChgCnt);
MessageBeep(0);
// Здесь мы не можем вызвать Table1.Refresh, делаем это позже.
PostMessage(Handle, WM_UPDATETABLE, 0, 0);
end;
// Данная функция вызывается в ответ на PostMessage (см. выше).
procedure TForm1.UpdateTableData(var Msg: TMessage);
begin
// Не пытайтесь вызвать обновление, если мы в "середине" редактирования.
if (Table1.State = dsBrowse) then
Table1.Refresh;
end;
procedure TForm1.Table1AfterOpen(DataSet: TDataSet);
begin
// Установка обратного вызова.
FCB := TBDECallback.Create(Self, Table1.Handle, cbTableChanged,
nil, 0, TableChangeCallBack);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Table1.DatabaseName := ExtractFilePath(ParamStr(0));
Table1.Open;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
SeqNo: Longint;
begin
// События таймера просто осуществляют вызов DbiGetSeqNo для получения доступа к таблице.
// В противном случае мы не хотим делать обратный вызов, пока что-то делаем
// (типа прокрутки) для получения доступа к данным. DbiGetSeqNo вызывается в случае,
// если таблица не активна.
if Table1.State <> dsInActive then
DbiGetSeqNo(Table1.Handle, SeqNo);
end;
end.
TCMAIN.TXT:
object Form1: TForm1
Left = 270
Top = 230
Width = 361
Height = 251
Caption = 'Form1'
PixelsPerInch = 96
OnCreate = FormCreate
TextHeight = 13
object DBGrid1: TDBGrid
Left = 0
Top = 83
Width = 353
Height = 141
Align = alBottom
DataSource = DataSource1
TabOrder = 0
end
object DBNavigator1: TDBNavigator
Left = 96
Top = 4
Width = 240
Height = 25
DataSource = DataSource1
TabOrder = 1
end
object Button1: TButton
Left = 132
Top = 36
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 2
OnClick = Timer1Timer
end
object Table1: TTable
AfterOpen = Table1AfterOpen
DatabaseName = 'DBDEMOS'
TableName = 'VENDORS.DB'
Left = 16
Top = 8
end
object DataSource1: TDataSource
DataSet = Table1
Left = 52
Top = 8
end
object Timer1: TTimer
OnTimer = Timer1Timer
Left = 80
Top = 28
end
end
- Mark Edington
Взято из
Советов по Delphi от
Сборник Kuliba
Данный совет показывает как в Delphi 2.01 можно использовать функцию BDE DbiCallBack для получения значения линейки прогресса при длительных пакетных операциях, связанных с движением данных.
Дополнительная документация, описывающая вызовы функций BDE, находится в файле BDE32.HLP (расположенном в каталоге, где установлен 32-битный IDAPI).
При создании функций обратного вызова BDE, BDE будет осуществлять "обратный вызов" функций вашего приложения, позволяя тем самым извещать ваше приложение о происходящих событиях, а в некоторых случаях передавать информацию обратно BDE.
BDE определяет несколько возвращаемых типов, которые могут быть установлены для обратного вызова:
состояние больших пакетных операций.
запросы для передачи информации вызывающему оператору.
Данный совет подробно описывает обратный вызов типа cbGENPROGRESS, позволяющий изменять полоску прогресса в соответствии с состоянием операции.
Чтобы это сделать, необходимо сперва вызвать функцию DbiGetCallBack(), возвращающую дескриптор обратного вызова, который мог быть уже установлен (с этими параметрами), и сохранить информацию в структуре данных. Затем установить свой обратный вызов, заменяя им любой установленный до этого.
При установке вашего обратного вызова вам понадобится передавать BDE указатель на структуру данных, содержащую информацию о предыдущем установленном обратном вызове, после чего, при выполнении вашей функции обратного вызова, вы можете воспользоваться оригинальным обратным вызовом (если он установлен).
BDE каждый раз возвращает вашему приложению сообщение, содержащее количество обработанных записей, или же процентное соотношение обработанных записей, также передаваемое в виде целого числа. Ваш код должен учитывать эту ситуацию. Если процентное поле в структуре обратного вызова больше чем -1, можно сделать вывод что передан процент и можно сразу обновить линейку прогресса. Если же это поле меньше нуля, обратный вызов получил текстовое сообщение, помещенное в поле szTMsg и содержащее количество обработанных записей. В этом случае вам понадобится осуществить грамматический разбор текстового сообщения, преобразовать остальные строки в целое, затем вычислить текущий процент обработанных записей, и только после этого изменить линейку прогресса.
Наконец, после осуществления операции с данными, вам необходимо "отрегистрировать" ваш обратный вызов, и вновь установить предыдущую функцию обратного вызова (если она существует).
Для следующего примера необходимо создать форму и расположить на ней две таблицы, компонент ProgressBar и кнопку.
unitTestbc1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Grids, DBGrids, DB, DBTables, ComCtrls;
type
TForm1 = class(TForm)
Table1: TTable;
BatchMove1: TBatchMove;
Table2: TTable;
Button1: TButton;
ProgressBar1: TProgressBar;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses Bde; {Здесь расположены Dbi Types и Procs}
{$R *.DFM}
{тип структуры данных для сохранения информации о предыдущем обратном вызове}
type
TDbiCbInfo = record
ecbType: CBType;
iClientData: longint;
DataBuffLn: word;
DataBuff: pCBPROGRESSDesc;
DbiCbFn: pointer;
end;
type
PDbiCbInfo = ^TDbiCbInfo;
{Наша функция обратного вызова}
function DbiCbFn(ecbType: CBType;
iClientData: Longint;
CbInfo: pointer): CBRType stdcall;
var
s: string;
begin
{Проверяем, является ли тип обратного вызова тем, который мы ожидаем}
if ecbType = cbGENPROGRESS then
begin
{если iPercentDone меньше нуля, извлекаем число}
{обработанных записей из параметра szMsg}
if pCBPROGRESSDesc(cbInfo).iPercentDone < 0 then
begin
s := pCBPROGRESSDesc(cbInfo).szMsg;
Delete(s, 1, Pos(': ', s) + 1);
{Вычислям процент выполненного и изменяем линейку прогресса}
Form1.ProgressBar1.Position :=
Round((StrToInt(s) / Form1.Table1.RecordCount) * 100);
end
else
begin
{Устанавливаем линейку прогресса}
Form1.ProgressBar1.Position :=
pCBPROGRESSDesc(cbInfo).iPercentDone;
end;
end;
{существовал ли предыдущий зарегистрированный обратный вызов?}
{если так - осуществляем вызов и возвращаемся}
if PDbiCbInfo(iClientData)^.DbiCbFn <> nil then
DbiCbFn :=
pfDBICallBack(PDbiCbInfo(iClientData)^.DbiCbFn)
(ecbType,
PDbiCbInfo(iClientData)^.iClientData,
cbInfo)
else
DbiCbFn := cbrCONTINUE;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
CbDataBuff: CBPROGRESSDesc; {Структура DBi}
{структура данных должна хранить информацию о предыдущем обратном вызове}
OldDbiCbInfo: TDbiCbInfo;
begin
{Убедимся в том, что перемещаемая таблица открыта}
Table1.Open;
{Убедимся в том, что таблица-приемник закрыта}
Table2.Close;
{получаем информацию о любом установленном обратном вызове}
DbiGetCallBack(Table2.Handle,
cbGENPROGRESS,
@OldDbiCbInfo.iClientData,
@OldDbiCbInfo.DataBuffLn,
@OldDbiCbInfo.DataBuff,
pfDBICallBack(OldDbiCbInfo.DbiCbFn));
{регистрируем наш обратный вызов}
DbiRegisterCallBack(Table2.Handle,
cbGENPROGRESS,
longint(@OldDbiCbInfo),
SizeOf(cbDataBuff),
@cbDataBuff,
@DbiCbFn);
Form1.ProgressBar1.Position := 0;
BatchMove1.Execute;
{если предыдущий обратный вызов существовал - вновь устанавливаем его,}
{в противном случае "отрегистрируем" наш обратный вызов}
if OldDbiCbInfo.DbiCbFn <> nil then
DbiRegisterCallBack(Table2.Handle,
cbGENPROGRESS,
OldDbiCbInfo.iClientData,
OldDbiCbInfo.DataBuffLn,
OldDbiCbInfo.DataBuff,
OldDbiCbInfo.DbiCbFn)
else
DbiRegisterCallBack(Table2.Handle,
cbGENPROGRESS,
longint(@OldDbiCbInfo),
SizeOf(cbDataBuff),
@cbDataBuff,
nil);
{Показываем наш успех!}
Table2.Open;
end;
end.
Взято из
Deploying Midas
Deploying Midas
You need to purchase a MIDAS license for the application server. When you purchase a MIDAS license you receive a CD with the MIDAS suite for install. You need to install the
EXE's and DLL's that you created for your app, the rest (which also includes the runtime packages, BDE, SQL Links, etc.) are installed by the MIDAS software.
DerectX, OpenGL
DerectX, OpenGL
Cодержание раздела:
DFM -->TXT, TXT --> DFM
DFM -->TXT, TXT --> DFM
Use the Convert.exe file found in the Delphi\C++ Builder
bin directory.
Example:
C:\Delphi\convert enum.dfm enum.txt
C:\Delphi\convert enum.txt enum.dfm
Диалог прекращения печати
Диалог прекращения печати
Как мне создать диалог прекращения печати при работе с TPrinter?
Создайте форму с кнопкой "Abort". Обработчик нажатия кнопки должен вызывать Printer.Abort.
Теперь, при запуске печати, вам необходимо показать этот диалог в немодальном режиме методом Show(). Тем не менее, перед показом диалога необходимо деактивировать главную форму приложения, например так:
Application.MainForm.Enabled:= false;
AbortDlg.Show;
{ Здесь код печати }
AbortDlg.Close;
Appliction.MainForm.Enable := true;
Имейте в виду, что для правильной логики работы необходимо проверять значение свойства Printer.Aborted. Если пользователь нажал кнопку прекращения печати, эта переменная укажет о необходимости выхода из подпрограммы печати. Но здесь есть небольшой подвох. Printer.Abort предполагает прерывание печати вызовом функции WinProcs.AbortDoc(), но он не делает этого (по крайней мере в Delphi 1). Следовательно, исправляя ошибку Borland, вы должны это делать сами в ответ на нажатие кнопки Abort (в обработчике события onClick)
Взято из
Советов по Delphi от
Сборник Kuliba
Диалоги
Диалоги
Cодержание раздела:
Примечание: в раздел помещены не только диалоги VCL, но и диалоги Windows.
См. также статьи в других разделах:
Динамические и виртуальные методы
Динамические и виртуальные методы
Согласно онлайновой документации, динамические и виртуальные методы семантически идентичны, единственно различие заключается в их реализации, нижеследующий код генерирует указанную ошибку компиляции:
typet = class
function a: integer; {статический}
function b: integer; virtual;
function c: integer; dynamic;
property i: integer read a; { ok }
property j: integer read b; { ok }
property k: integer read c;{ ОШИБКА: type mismatch (не совпадение типа) }
end;
Взято из
Советов по Delphi от
Сборник Kuliba
Динамические создание объектов в TabbedNotebook
Динамические создание объектов в TabbedNotebook
procedureTForm1.TabbedNotebook1Click(Sender: TObject);
var
myE: TEdit;
begin
with TabbedNotebook1 do
begin
if PageIndex = 1 then
begin
myE := TEdit.Create(Self);
myE.Left := 12;
myE.Top := 12;
myE.Parent := Pages.Objects[PageIndex] as TWinControl;
myE.Show;
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Button2:Tbutton;
begin
button2:=tbutton.create(self);
button2.parent:=TabbedNotebook1.Pages.Object[0] as TTabPage;
button2.setbounds(30,30,60,30);
end;
Взято с
Динамическое создание полей
Динамическое создание полей
var
I: Integer;
Field: TField;
begin
{ Поля можно добавлять только к неактивному набору данных. }
Table1.Active := False;
{ Распределяем определенные поля если набор данных еще не был активным. }
Table1.FieldDefs.Update;
{ Создаем все поля из определений и добавляем к набору данных. }
for I := 0 to Table1.FieldDefs.Count - 1 do
begin
{ Вот где мы действительно сообщаем набору данных о необходимости создания поля. }
{ Поле "назначается", но нам нужно не это, нам нужна просто ссылка на новое поле. }
Field := Table1.FieldDefs[I].CreateField(Table1);
end;
{ Вот пример того, как вы можете добавить дополнительные, вычисленные поля }
Field := TStringField.Create(Table1);
Field.FieldName := 'Total';
Field.Calculated := True;
Field.DataSet := Table1;
{ Теперь мы можем увидеть наши поля. }
Table1.Active := True;
end;
Взято из
DirectX для начинающих. Часть вторая
DirectX для начинающих. Часть вторая
Прошёл месяц с тех пор как я написал первую часть ( http://www.delphikingdom.com/helloworld/directx.htm ) статьи по использованию DirectX в среде Delphi. У меня накопилось ещё несколько примеров, которые, надеюсь, послужат наглядным руководством для начинающих.
Прежде, чем описывать предложенные общему вниманию программы, хочу сообщить о некоторых изменениях в их коде по сравнению с примерами первой статьи, чтобы не останавливаться впоследствии на этих мелких деталях.
Вызовы _AddRef() и _Release() больше не используются ? в конце концов я посчитал это бессмысленной тратой времени при наборе кода. К тому же, как выяснилось, что вызов именно этих методов привёл к неработоспособности одного из примеров предыдущей статьи ? если кто интересовался, знает, что это был пример опроса клавиатуры с использованием DirectInput. После удаления вызовов программа стал работать корректно. По-видимому, имело место некорректное взаимодействие с драйвером клавиатуры.
Выражение вида if COM-объект <> nil then COM-объект := nil
переписано с использованием процедуры следующего вида:
procedureSAFE_DELETE(p: TInterfacedObject);
begin
if p <> nil then
p := nil
end;
Теперь достаточно написать SAFE_DELETE( @COM-объект ) ? может, это покажется и излишним, но поверьте, в более крупных программах, где надо удалить 15-20 COM-интерфейсов, это становится удобным и сокращает код. Все эти соображения навеяны под влиянием примеров из MS SDK. Кстати, может, кто-то несогласен с правильностью описанной процедуры?
Модуль basedd8.pas в проектах для DirectDraw переименован в basedd7.pas ? всё-таки DirectDraw ? это часть DirectX 7, в версий 8 он как таковой отсутствует.
В функции LoadFiles() добавлен вызов DeleteObject() ? как известно, после работы объекты GDI надо удалять, иначе они поглощают ресурсы системы. В данном случае именно такой объект создаётся при вызове функции GDI LoadImage() ? казалось бы, тип HBITMAP ? это всего лишь переопределение типа LongWord, копилятор самостоятельно удалит переменную этого типа после выхода из функции. На самом деле GDI при вызове LoadImage() (и других подобных функций) создаёт ресурс GDI и резервирует для него часть системной памяти, а переменная hBmp ? всего лишь идентификатор этого ресурса в общем списке ресурсов Windows. Поэтому в процессе выполнения программы будет удаляться только идентификатор, а ресурс, на который он указывает, будет «висеть» в памяти. Именно поэтому следует вызвать DeleteObject() для удаления объекта GDI. В предыдушем примере я не сделал этого по причине недосмотра.
Большая часть примеров в этой статье предназначена для работы с DirectDraw ? как мне кажется, наиболее востребованному элементу DirectX (кроме, естественно, Direct3D).
Надеюсь, мой стиль написания кода программ покажется удовлетворительным ? он почти во всём подобен стилю, который использовали составители DirectX SDK. Вообще, многие пишут, как курица лапой ? и предлагают свои творения на всеобщее обозрение. Ещё полезно заглянуть на страницу в нашем уважаемом Королевстве - http://www.delphikingdom.com/article/tassel.htm - это классика.
Почему я не рекомендую использовать DelphiX
Хочется поделиться с новичками своим мнением по поводу компонетов DelphiX и почему я не рекомендую их использовать.
С одной стороны, DelphiX - это удобно ? нет необходимости выполнять утомительный набор методов DirectX и длинных, как многоступенчатая ракета, констант наподобие DDENUMSURFACES_CANBECREATED. Однако давайте посмотрим ? используется что-нибудь подобное в С++? Я не могу исследовать всю Сеть в поисках овета на такой вопрос, но, думается ? нет. Почему?
Такие наборы классов ? это нестандартный подход. Допустим, вы потратили изрядно своего времени и досконально изучили DelphiX. Так вот, изучив всё ЭТО, вы в итоге не изучили сам DirectX. Второе ? изученные классы обладают многим и позволяют писать реальные программы, но всё равно этот подход очень негибок ? вы ограничены тем, что уже сделано. На этот счёт у меня есть веский аргумент ? это Direct3D. Вот тут DelphiX уж точно не даст развернуться как следует ? и это характерно и для других компонент DirectX, пусть и в меньшей мере. Третье ? немного, но снижается быстродействие. Четвёртое ? в классах DelphiX кроме непосредственных вызовов методов интерфейсов DirectX используются ещё и собственные функции ? кто даст гарантию, что в них нет ошибок?
В конце концов, именно за такие вот «примочки» Delphi не в почёте у С-программистов ? они попросту надсмехаются над такими методами разработки программ. К сожалению, должен к ним присоединиться и я. Как же так, возмутятся многие. Компонентный подход ? это ведь основа основ Delphi! Согласен, использование TMemo или TComboBox ? это действительно удобный подход, да что там ? превосходный, отличный подход! Но вот в случае с DirectX или чем-то подобным использовать такие средства разработки крайне нежелательно. Как бы вы отнеслись к компоненту TOpenGL? Или TWin32API? Вот так-то. DelphiX можно использовать как источник разных идей по реализации того или иного эффекта ? перенося всё это в свою программу в виде отдельных функций или собственноручно написанных классов. Так что изучайте прямой API ? для уверенности в завтрашнем дне и в собственной квалификации.
Собственно, сами примеры
Bounds
В предыдущей статье я предложил вниманию только один пример для DirectDraw ? простая реализация вывода спрайта поверх фона в задний буфер и перенос построенной картинки на экран. Спрайт не мог выйти за пределы экрана ? и это не спроста. Если убрать досадное ограничение, то выяснится, что как только часть спрайта выходит за границы экрана (а фактически границы заднего буфера), то сразу исчезает полностью. Возвращение в прежнюю позицию восстанавливает вывод.
Когда я впервые столкнулся с этой проблемой, меня это неприятно поразило. Оказалось, что необходимо использовать интерфейс отсечения ? IDirectDrawClipper. Однако он предназначен только для оконных приложений, в полноэкранном режиме от него нет никакого проку. Как уже было упомянуто, рекомендую программировать только полноэкранные приложения как наиболее быстродействующие, и забыть об оконных. Так как же быть ? тупик?
К сожалению, даже в SDK нет примеров решения этого вопроса. Впрочем ответить на него не так уж сложно ? нужно лишь понять, что хочет DirectDraw и как это преподнести.
Корень проблемы в том, что как только при копировании методом BltFast() часть поверхности выходит за край той поверхности, на которую она копируется (обычно задний буфер), вывод не осуществляется. В чём причина такого нелепого ограничения ? думается, опять же в обеспечении наибольшего быстродействия. Например, вы планируете создать игру типа Tetris, а не скроллинговую стрелялку, и все ваши спрайты будут двигаться только в пределах экрана ? но вот DirectDraw всё равно пришлось бы проверять их выход за границы, даже при отсутствии в этом необходимости. Хотя эту проблему можно было бы решить с помощью флагов при создании конкретной поверхности, но Microsoft этого не сделала. Ну что же, сделаем за неё эту работу.
Обратите внимание на четвёртый параметр метода IDirectDrawSurface7.BltFast() ? это адрес структуры типа TRect. Для чего он нужен? Как известно, назначение струтуры TRect в GDI API ? указание положения и размера какой либо области путём задания левого верхнего и правого нижнего угла. Так вот, эта структура позволяет указать DirectDraw о необходимости вывести не всё изображение спрайта, а лишь его часть:
Воспользуемся этой структурой и будем вместо всего спрайта выводить какую-то его облать ? ту, которая будет видна на экране, а невидимая будет находиться вне области, описываемой структурой TRect. Т. о. будет создаваться лишь иллюзия пребывания спрайта за пределами экрана. Например, спрайт выходит за границы экрана, как это показано на самом первом рисунке. Тогда выводимая область должна быть такой (показана красным цветом):
Теперь повоображайте и нарисуйте на бумаге, как будут выглядеть выводимые части спрайта при различных его положениях за границами экрана.
Вот код, ответственный за вывод части изображения:
// Предполагаем, что края спрайта не выходят за границы экрана
SetRect(rRect, 0, 0, SPRITE_WIDTH, SPRITE_HEIGHT);
// Проверяем выход кра?в, и если такая ситуация имеет место, то корректируем
// положение области копирования на поверхности спрайта
if nX < 0 then
rRect.Left := -nX;
if nY < 0 then
rRect.Top := -nY;
if nX + SPRITE_WIDTH > SCREEN_WIDTH then
rRect.Right := SCREEN_WIDTH - nX;
if nY + SPRITE_HEIGHT > SCREEN_HEIGHT then
rRect.Bottom := SCREEN_HEIGHT - nY;
Где nX и nY ? координаты левого верхнего угла спрайта. При выводе надо не забыть скорректировать их:
nX + rRect.Left, nY + rRect.Top
Вот и всё. Запустите проект на выполнение ? вы увидите, что теперь свободно отбражается даже часть спрайта. Выведите его полностью за пределы экрана ? начнёт жужжать встроенный динамик компьютера ? эта возможность введена для проверки правильности алгоритма. Кстати, если необходимо вывести всю поверхность изображения, вместо адреса структуры следует передать nil ? как это сделано для фона.
Scale
Иногда при выводе может понадобится растянуть или сжать объект по осям или просто увеличить или уменьшить его ? для подобных эффектов DirectDraw предоставляет метод IDirectDrawSurface.Blt(). Он является хотя и более медленным, чем BltFast() ? однако при этом более функционален. Так вот, мы снова будем указывать с помощью структуры TRect область вывода изображения ? но уже на поверхности-приёмнике данных. Изменяя её размеры, можно добиться пропорционального или непропорционального изменения масштаба изображения по осям X и Y. Думаю, нет надобности описывать действия которые происходят в процедуре OnDraw(). Замечу лишь, что на современных видеокартах с полной аппаратной поддержкой DirectDraw эффект масштабирования выглядит гораздо привлекательнее, чем на «ветеранах».
Transparent
При выводе изображения на экран нередко встаёт проблема затирания заднего фона, а говоря языком профессионалов ? проблема вывода нерегулярных спрайтов. Нерегулярный спрайт ? это обычный прамоугольный спрайт, который содержит маску для указания прозрачных участков изображения. Пиксели, принадлежащие этим участкам, при выводе игнорируются ? создаётся иллюзия прозрачности. В GDI нет прямого способа вывести нерегулярный спрайт ? для этого необходимо подготовить изображение и отдельно маску. В чём недостатки такого подхода? Их много. Во-первых, требуется иметь два изображения ? это увеличивает объём данных в памяти и на диске. Во-вторых, скорость вывода данного изображения падает вдвое, а ведь GDI и без этого не славится своей скоростью. В третьих, это дополнительная забота того, кто готовит изображение в графическом редакторе. В своё время автор даже написал небольшую утилиту, которая создавала маску для выбранного изображения и записывала её в отдельный файл. Но теперь об этом можно забыть.
DirectDraw предоставляет удобный инструмент для задания маски прозрачности. Цвета пикселей, которые игнорируются, называются «цветовыми ключами». Каждая поверхность может иметь свои цветовые ключи, причём их может быть несколько. Следующий фрагмент кода создаёт и присоединяет к поверхности «цветовой ключ», цвет которого ? чёрный.
var
ddck: TDDCOLORKEY;
begin
ddck.dwColorSpaceLowValue := 0;
ddck.dwColorSpaceHighValue := ddck.dwColorSpaceLowValue;
pSprite.SetColorKey(DDCKEY_SRCBLT, @ddck);
Для указания прозрачного цвета, как видно, используется структура TDDCOLORKEY. В её двух полях необходимо указать нижнюю и верхнюю границу диапазона «прозрачных» цветов. Замечу, что использование диапазона цветов возможно только в случае, если такая возможность поддерживается аппаратно. Поэтому лучше ограничиться каким-либо одним цветом, как это сделано выше. После заполнения структуры TDDCOLORKEY необходимо вызвать метод IDirectDrawSurface7.SetColorKey(), где первый параметр ? один из возможных флагов, второй - адрес структуры TDDCOLORKEY. Обычно используется флаг DDCKEY_SRCBLT, который указывает, что при копировании изображения будет использоваться цветовой ключ поверхности-источника. Другие флаги можно узнать из справочной службы DirectX SDK.
Теперь о главном. В приведённом выше фрагменте кода в качестве маски задаются пиксели чёрного цвета. Как известно, нулевое значение обозначает отсутствие цвета во всех графических режимах ? 16 цветов, 256, 65535 и т.д.
Поэтому можно смело присваивать 0 для чёрной маски в любом режиме. Однако, предположим, нам надо задать цветовой ключ в виде чистого синего цвета. Для 24- и 32-битного режима это можно сделать с помощью макроса (функции) из модуля windows.pas:
function RGB(r, g, b: Byte): COLORREF;
begin
Result := (r or (g shl 8) or (b shl 16));
end;
Зарезервированное слово shl относится к сдвиговым операциям и сдвигает содержимое на указанное значение влево.
Т.к. в этих графических режимах каждый из трёх цветов (красный, синий и зелёный) кодируется одним байтом, то значение каждого параметра функции должно лежать в пределах от 0 до 255. Вот как это можно представить графически:
Так, для задания цветового ключа в виде чистого синего цвета необходимо написать так:
ddck.dwColorSpaceLowValue := RGB( 0, 0, 255 );
ddck.dwColorSpaceHighValue := ddck.dwColorSpaceLowValue;
Ну и в том же духе, в полном соответствии с теорией цвета. А теперь попробуйте задать цветовой ключ для 16-битового режима. Ничего не получится. Почему? Дело в том, что цвет пикселя хранится в ячейке длиной в 16 бит, а цветовых составляющих ? 3, появляется лишний бит, который чаще отдаётся зелёному цвету, а иногда просто не используется. Формат, где теряется лишний бит, обозначается 5-5-5 (на каждую цветовую составляющую по пять бит, а не одному байту), другой формат обозначается 5-6-5 (на зелёную составляющую выделяется 6 бит ). Понятно, что задание цвета с помощью функции RGB() для таких форматов ни к чему ни приведёт.
В своё время я довольно долго промучился с этой проблемой, тем более что в имеющейся литературе ничего об этом не сказано. В конце концов решил, что необходимо написать аналогичную к RGB() функцию, но об этом немного позже. Давайте сначала выясним, какой же формат использует установленная на нашем компьютере видеокарта. DirectDraw позволяет узнать это с помощью функции IDirectDrawSurface7.GetPixelFormat(). Единственным параметром необходимо передать адрес структуры TDDPIXELFORMAT. Вот фрагмент соответствующего кода:
var
ddpf: TDDPIXELFORMAT;
begin
ZeroMemory(@ddpf, SizeOf(TDDPIXELFORMAT));
ddpf.dwSize := SizeOf(TDDPIXELFORMAT);
pSprite.GetPixelFormat(ddpf);
Формат цветовых составляющих описывается в полях dwRBitMask, dwGBitMask и dwBBitMask структуры TDDPIXELFORMAT ? но только в том случае, если битовое поле dwFlags содержит флаг DDPF_RGB ? признак того, что поверхность создана в RGB-режиме. Значения полей dwRBitMask, dwGBitMask и dwBBitMask для режимов с разной глубиной палитры описываются в разделе dwRBitMask, dwGBitMask и dwBBitMask справочной службы DirectX SDK:
DDPF_RGB 16 R: 0x0000F800
G: 0x000007E0
B: 0x0000001F
A: 0x00000000
DDPF_RGB 16 R: 0x0000001F
G: 0x000007E0
B: 0x0000F800
A: 0x00000000
DDPF_RGB 16 R: 0x00007C00
G: 0x000003E0
B: 0x0000001F
A: 0x00000000
Запустите готовое приложение GetPixFormat из каталога DXCommon ? и посмотрите, какой формат поверхности использует ваша карта в 16-битовом режиме. Скажу, что на компьютере с видеоакселератором GeForce 2 MX 420 получались значения из самой верхней ячейки ? и это соответствует формату 5-6-5. По-моему, именно такой формат принят во всех современных видеокартах (заметьте, что во второй ячейке таблицы составляющие R и B переставлены местами). А вот, например, дедушка S3 Trio 3D/2X использует формат, описанный в нижней ячейке ? опытным путём установлено, что это 5-5-5.
Вот как должен быть переписан макрос для формата 5-6-5:
function RGB565(r, g, b: Byte): COLORREF;
begin
Result := ((r shl 11) or (g shl 5) or b);
end;
Графически битовая маска может быть представлена так:
А вот как должен выглядеть макрос для формата 5-5-5:
function RGB555(r, g, b: Byte): COLORREF;
begin
Result := ((r shl 10) or (g shl 5) or b);
end;
Графически битовая маска может быть представлена так:
Как видно, последний бит не используется. Обратите внимание, что группа битов, отвечающих за красную и синию составляющую, в 16-битовом режиме поменялись старшинством. А формат, описанный во второй ячейке таблицы, наоборот, по старшинству схож с 24- и 32-битовым режимами. Подозреваю, что используется этот формат довольно редко.
Естетственно, что теперь максимальное значение, передаваемое в макросы (функции) RGB565() и RGB555(), соответствует значению 31, а для задания читого зелёного цвета в режиме 5-6-5 необходимо указать RGB565( 0, 63, 0 ), т. к. битов 6.
Для того, чтобы наша DirectDraw-программа без проблем работала в обоих форматах, необходимо проверить текущий формат, запомнить его и при задании цветового ключа для поверхности вызвать соответствующий макрос. Всё это и делается в приложении Transparent ? надеюсь при его разборе у вас не возникнет проблем. Не забудьте при копировании методом BltFast() указать флаг DDBLTFAST_SRCCOLORKEY.
Fps
Ещё один полезный пример ? вывод текста на поверхность DirectDraw ? в виде значения fps. Сама компонента не обладает такими средствами ? DirectDraw изначально создавался лишь для максимально быстрого копирования одного изображения на другое. Для вывода текста необходимо использовать GDI.
Как неоднократно упоминалось, GDI очень медленнен, и вывод текста ? одна из функций, которая серьёзно может «притормозить» DirectDraw-программу. Поэтому необходимо пользоваться этой функцией как можно реже. Для взаимодействия DirectDraw c GDI введён простой метод IDirectDrawSurface7.GetDC(). Получив контектс, можно спокойно чертить в нём всеми мыслимыми функциями GDI. Метод IDirectDrawSurface7.ReleaseDC() переносит содержимое контекста в область памяти, занятую поверхностью DirectDraw и удаляет контекст.
Откройте файл проекта fps.dpr. Т. к. функция TextOut() уже занята, функцию, отвечающую за вывод текста, пришлось назвать менее звучно ? OutText(). Я не буду подробно описывать её, надеюсь, всё понятно. Для ускорения работы программы я поступил так: для вывода текста используется отдельная поверхность ? именно на неё и выводится текст средствами GDI. Затем всё время поверхность просто копируется на задний буфер ? это осуществляется гораздо быстрее, чем постоянный вывод текста на задний буфер, а когда появляется необходимость изменить текст ? он снова выводится на нашу отдельную поверхность. Потребность изменить текст появляется лишь раз в секунду.
Для вызова OutText() я использовал мультимедиа-таймер Windows. Значение fps наращивается при каждом построении кадра и обнуляется после вызова OutText().
И последнее. По-видимому, в операционной системе Windows 2000 функции GDI должны работать быстрее, т. к. эта ОС полностью 32-х разрядная. Но всё же рекомендую пользоваться описанным выше подходом.
Text
Ещё один пример вывода текста ? но уже на задний буфер. Добавлен мною для полноты темы. Текст заданных размеров постоянно выводится на задний буфер без поверхности-посредника. При выводе текста я столкнулся с одной проблемой ? это сглаживание краёв символов. В модуле windows.pas описана константа ANTIALIASED_QUALITY, но её задание в параметре fdwQuality функции CreateFont() ни к чему ни привело. Может быть, в Windows 9x и МЕ это значение не используется? Во всяком случае, константы ANTIALIASED_QUALITY и NONANTIALIASED_QUALITY в справке Delphi Help не описаны.
Sound
Эта программа ? прямое продолжение моего первого примера по использованию DirectSound. Введено ряд усовершенствований:
1. Файл lowfunc.pas теперь полностью закончен и является практически прямым переводом файла wavread.cpp. Выражаю благодарность Max Morozov и iXania, которые помогли мне перевести некоторые сложные конструкции с языка C++ на Object Pascal, т. к. самому мне для этого не хватило квалификации. Теперь нет необходимости использовать отдельную динамическую библиотеку ? весь код располагается в exe-файле. Всем спасибо.
2. Я решил написать небольшой класс TWave ? он сам заботится об открытии звукового файла, чтении данных из него в звуковой буфер и проигрывании их. Функциональность класса не полная ? это лишь пример. Благодаря ООП главный модуль main.pas серьёзно уменьшился, теперь для воспроизведения wav-файла средствами DirectSound достаточно написать:
var
sound: TWave;
begin
sound := TWave.Create();
sound.OpenWaveFile('wavefile.wav');
sound.Play();
Правда, просто?
GetDXVer и GetDXVerSetup
Я решил заглянуть в некоторые области DirectX, до которых руки многих авторов книг по DirectX попросту «не доходят». Например, написание программы для определения текущей версии DirectX. Иногда это может быть очень полезно.
Первый пример, который я предлагаю вашему вниманию ? это GetDXVer. Это аналог из DirectX SDK для Visual C++. Функция GetDXVersion() ответственна за получение намера текущей версии DirectX. Каким образом она действует? Механизм прост, но достаточно громозд. Сначала загружается нужная динамическая библиотека из комплекса DirectX, например DDRAW.DLL или DINPUT.DLL. Затем получают адреса функций, которые экспортируют эти библиотеки ? это «создающие» функции наподобие DirectDrawCreate() или DirectInputCreateA(). Затем при помощи этих функций и создаются нужные интерфейсы вроде IDirectDraw и т.п. Если на каком-то шаге происходит сбой, это означает, что данная функция или интерфейс не поддерживаются. Зная, в какой версии появился тот или иной интерфейс, можно выяснить текущую версию DirectX. Ещё одна функция, GetDXRegVersion(), извлекает полный порядковый номер из реестра Windows. Кстати, эта функция может в принципе читать любой строковый параметр из реестра и делает ненужным использование класса TRegistry, что очень важно, если мы хотим получить маленький по размерам исходный модуль.
Пример имеет два недостатка:
1. Работает с некоторой задержкой. Для создания всех интерфейсов требуется некоторое время. Особо медленно создаётся интерфейс IDirectMusic.
2. Программа не способна определить номер версии, если он выше 8 ? это принципиальный барьер.
Ещё один пример ? GetDXVerSetup ? использует специальную функцию DirectXSetupGetVersion() из библиотеки dsetup.dll. эта библиотека не входит в стандартный run-time DirectX, а поставляется только с setup-программами установки DirectX на компьютер пользователя. При написании этого примера я столкнулся с двумя проблемами:
1. В Help-службе DirectX SDK 7 указаны такие возможные значения, которые могут быть помещены в переменную pdwVersion при вызове функции DirectXSetupGetVersion():
DirectX version Value pointed to by pdwVersion
DirectX 1 0x00040001
DirectX 2 0x00040002
DirectX 3 0x00040003
DirectX 5.0 0x00040005
DirectX 6.0 0x00040006
DirectX 7.0 0x00040007
А вот в Help-службе DirectX SDK 8 указаны такие:
DirectX version Value pointed to by pdwVersion
DirectX 1 0x00000000
DirectX 2 0x00000000
DirectX 3 0x00000000
DirectX 5.0 0x00040005
DirectX 6.0 0x00040006
DirectX 7.0 0x00040007
DirectX 8.0 0x00040008
Зачем понадобилось обозначить версии 1, 2 и 3 как отсутствие DirectX ? непонятно. Может быть, Microsoft посчитала, что эти ранние версии уже слишком устарели и не обеспечивают пользователя нужными мультимедиа-средствами? А раз так, то может быть лучше вообще известить об отсутствии DirectX? Может статься, что это просто ошибка в файле справки. Второй вариант более правдоподобен, первый ? забавен.
Используя файл directsetup.pas, мне не удалось экспортировать функцию DirectXSetupGetVersion() из библиотеки dsetup.dll. Дело в том, что она ищется в каталоге, по-видимому, указанном в ключе реестра HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\DirectXDrivers, но я не обнаружил такой ключ в своём реестре. Так что воспользоваться этим файлом не представилось возможным. К тому же структуры TDirectXRegisterAppW2 не существует ? приехали!
Я самостоятельно перевёл файл dsetup.h из SDK 8 в файл dsetup.pas, пытаясь максимально точно соблюдать синтаксис структур и параметро функций. Может быть, кто-то им воспользуется.
Недостаток приведенного метода в том, что вам придётся постоянно «таскать» библиотеку dsetup.dll вместе с исходной программой.
Заключение
Написание подобных статей ? хороший стимул к тщательному изучению DirectX. Скажу, что чем больше работаю с этим комплексом, тем больше разных нюансов всплывает на поверхность.
Надеюсь, что мои усилия хоть как-то помогут остальным желающим освоить этого игрового «монстра». Я надеюсь продолжить изучение DirectX и как только получится создать что-то стоящее, попробую поделиться сделанным с остальными.
Напоследок хочу выразить особую благодарность Антону Ржешевскому за его дельные советы в освоении DirectX и не только.
Автор: Виктор Кода