А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Функция bind
предназначена для сокетов, реализующих разные протоколы из разных стеков, поэтому кодирование адреса в ней сделано достаточно универсальным. Впрочем, следует отметить, что разработчики модуля WinSock
для Delphi выбрали не лучший способ перевода прототипа этой функции на Паскаль, поэтому универсальность в значительной мере утрачена. В оригинале прототип функции bind
имеет следующий вид:
int bind(SOCKET s, const struct sockaddr FAR* name, int namelen);
Видно, что второй параметр — это указатель на структуру sockaddr
. Однако C/C++ позволяет при вызове функции в качестве параметра передать указатель на любую другую структуру, если будет выполнено явное приведение типов. Для каждого семейства адресов предусмотрена своя структура, и в качестве фактического параметра передастся указатель на эту структурy. Если бы авторы модуля WinSock описали второй параметр как параметр-значение типа указатель, можно было бы поступать точно так же. Однако они описали этот параметр как параметр-переменную. В результате на двоичном уровне ничего не изменилось: и там, и там в стек помещается указатель. Однако компилятор при вызове функции bind
не допустит использования никакой другой структуры, кроме TSockAddr
, а эта структура не универсальна и удобна, по сути дела, только при использовании стека TCP/IP. В других случаях наилучшим решением будет самостоятельно импортировать функцию bind
из wsock32.dll с нужным прототипом. При этом придется импортировать и некоторые другие функции, работающие с адресами. Впрочем мы здесь ограничиваемся только протоколами TCP и UDP, поэтому больше останавливаться на этом вопросе не будем.
На самом деле существует способ передать в функцию bind
с таким прототипом параметр addr
любого типа, совместимого с этой функцией. Если A
— некая переменная типа, отличающегося от TSockAddr
, то передать в качестве параметра-переменной ее можно так: PSockAddr(@А)^
. Однако подобные низкоуровневые операции программу не украшают.
В стандартной библиотеке сокетов (т.е. в заголовочных файлах для этой библиотеки) полагается, что адрес кодируется структурой sockaddr
длиной 16 байтов, причем первые два байта этой структуры кодируют семейство протоколов, а смысл остальных зависит от этого семейства. В частности, для стека TCP/IP семейство протоколов задается константой PF_INET
. (Ранее мы уже встречались с термином "семейство адресов" и константой AF_INET
. В ранних версиях библиотеки сокетов семейства протоколов и семейства адресов были разными понятиями, но затем эти понятия слились в одно, и константы AF_XXX
и PF_XXX
стали взаимозаменяемыми). Остальные 14 байтов структуры sockaddr
занимает массив типа char
(напомним, что тип char
в C/C++ соответствует одновременно двум типам Delphi: Char
и ShortInt
). В принципе, в стандартной библиотеке сокетов предполагается, что структура, задающая адрес, всегда имеет длину 16 байтов, но на всякий случай предусмотрен третий параметр функции bind
, который хранит длину структуры. В сокетах Windows длина структуры может быть любой (это зависит от протокола), так что этот параметр, в принципе, может пригодиться.
Ранее уже упоминалось, что неструктурированное представление адреса в виде массива из 14 байтов бывает неудобно, и поэтому для каждого семейства протоколов предусмотрена своя структура, учитывающая особенности адреса. В частности, для протоколов стека TCP/IP используется структура sockaddr_in
, размер которой также составляет 16 байтов. Из них задействовано только восемь: два для кодирования семейства протоколов, четыре для IP-адреса и два — для порта. Оставшиеся 8 байтов должны содержать нули.
Можно было бы предположить, что типы TSockAddr
и TSockAddrIn
, описанные в модуле WinSock, соответствуют структурам sockaddr
и sockaddr_in
, однако это не так. На самом деле эти типы описаны следующим образом (листинг 2.1).
TSockAddr
и TSockAddrIn
SunB = packed record
s_b1, s_b2, s_b3, s_b4: u_char;
end;
SunW = packed record
s_w1, s_w2: u_short;
end;
in_addr = record
case Integer of
0: (S_un_b: SunB);
1: (S_un_w: SunW);
2: (S_addr: u_long);
end;
TInAddr = in_addr;
sockaddr_in = record
case Integer of
0: (
sin_family: u_short;
sin_port: u_short;
sin_addr: TInAddr;
sin_zero: array[0..7] of Char);
1: (
sa_family: u_short;
sa_data: array[0..13] of Char);
end;
TSockAddrIn = sockaddr_in;
TSockAddr = sockaddr_in;
Таким образом, типы TSockAddr
и TSockAddrIn
— это синонимы типа sockaddr_in
(но не того sockaddr_in
, который имеется в стандартной библиотеке сокетов, а типа sockaddr_in
, описанного в модуле WinSock
). А тип sockaddr_in
из WinSock
является вариантной записью, и в случае 0 соответствует типу sockaddr_in
из стандартной библиотеки сокетов, а в случае 1 — sockaddr
из этой же библиотеки. Вот такая несколько запутанная ситуация, хотя на практике все выглядит не так страшно.
Из названия типов можно сделать вывод, что тип u_short
— это Word
, а u_long
— Cardinal
. На самом деле u_short
— это действительно Word
, а вот u_long
— это LongInt
. Сложно сказать почему выбран знаковый тип там, где предполагается беззнаковый. Видимо, это осталось в наследство от старых версий Delphi, которые не поддерживали тип Cardinal
в полном объеме. Кстати, тип u_char
— это Char
, а не Byte
.
Перейдем, наконец, к более практически важному вопросу: какими значениями следует заполнять переменную типа TSockAddr
, чтобы при передаче ее в функцию bind
сокет был привязан к нужному адресу. Так как мы ограничиваемся рассмотрением протоколов TCP и UDP, нас не интересует та часть вариантной записи sockaddr_in
, которая соответствует случаю 1, т.е. мы будем рассматривать только те поля этой структуры, которые имеют префикс sin
.
Поле sin_zero
, очевидно, должно содержать массив нулей. Это то самое поле, которое не несет никакой смысловой нагрузки и служит только для увеличения размера структуры до стандартных 16 байтов. Поле sin_family
, должно иметь значение PF_INET. В поле sin_port
записывается номер порта, к которому привязывается сокет. Номер порта должен быть записан в сетевом формате, т.е. здесь необходимо прибегать к функции htons
, чтобы из привычной нам записи номера порта получить число в требуемом формате. Номер порта можно оставить нулевым, тогда система выберет для сокета свободный порт с номером от 1024 до 5000.
IP-адрес для привязки сокета задается полем sin_addr
, которое имеет тип TInAddr
. Этот тип сам является вариантной записью, которая отражает три способа задания IP-адреса: в виде 32-битного числа, в виде двух 16-битных чисел или в виде четырех 8-битных чисел. На практике чаще всего встречается формат в виде четырех 8-битных чисел, реже — в виде 32-битного числа. Задание адресов в виде двух 16-битных чисел или двух 8-битных и одного 16-битного числа относится к очень редко встречающейся экзотике.
Интервал:
Закладка: