С чего начать?
В прошлой статье я кратко описал архитектуру Telephony API. Теперь
пришло время детализации и немного практики.… Практиковаться будем на
небольших экзамплах, которые будут постепенно увеличивать свои
возможности.… На этом этапе возникает резонный вопрос: «С чего же
начать разработку TAPI приложения?»
Начинать как всегда нужно с самого начала. Всё зависит от того, какое
приложение вы пишете. Допустим это графическое оконное приложение, в
которое вам необходимо добавить функции TAPI.... Писать приложение я
начну на ассемблере (хатчевский пакет MASM32), во-первых, потому, что
ассемблер - один из моих любимых языков (наряду с С++), во-вторых,
хатчевский МАСМ32 обладает очень читабельным синтаксисом. Единственный
минус – весьма и весьма паршивый инклюдный файл tapi32.inc из пакета.
Этим вопросом я сейчас занимаюсь и, надеюсь, скоро на tgl.h12.ru
появится новая, исправленная мною версия этого файла. Также приложение
будет чистым Win32 приложением, т.е. используются только функции Win
API. Незнакомым с этой темой, советую прочитать туториалы Iczelion’а на
тему Win32 API (есть на wasm.ru). В качестве среды разработки я выбрал
RadASM. Во-первых, на нём очень удобно разрабатывать большие
приложения! Во-вторых, у него неплохой набор шаблонов, что тоже не
может не радовать (лично меня бесит постоянно переписывать всякую чушь
типа структуры WNDCLASSEX :) ). В-третьих, это просто дело вкуса.
От автора: разработка пользовательского интерфейса выходит за рамки
этой серии статей, поэтому предполагается, что читатель имеет понятие о
том, что такое «окно», «класс окна» и т.д. и т.п. не на уровне постылых
компонентов, а на уровне Win32 API. Если же не знает, то я сказал, где
можно просветиться по этому поводу. Я же буду говорить только о
телефонии. Т.е., если мы, например, открываем линию (lineOpen), то вы
сами будете решать, где и когда в приложении это делать (при нажатии на
какую-либо кнопку, или при создании окна, или ещё где-то), это уже
вопрос проектирования приложения.
Итак, канализация. Ой, извиняюсь… Инициализация. Первый шаг при
разработке TAPI приложения. При инициализации происходят следующие
фундаментальные события:
1) включается сервер TAPI (TAPISRV);
2) осуществляется подключение к этому серверу;
3) сервер производит все необходимые установки.
От автора: ещё раз напомню, что предметом изучения является TAPI 2.х
(C/API), а не TAPI 3.х (COM/API). Поэтому, всё, сказанное ниже, не
будет относиться к TAPI 3.х.
Для инициализации необходимо вызвать функцию lineInitializeEx. Вот её сигнатура (из MSDN):
LONG WINAPI lineInitializeEx( LPHLINEAPP lphLineApp, HINSTANCE hInstance, LINECALLBACK lpfnCallback, LPCSTR lpszFriendlyAppName, LPDWORD lpdwNumDevs, LPDWORD lpdwAPIVersion, LPLINEINITIALIZEEXPARAMS lpLineInitializeExParams );
Параметры
lphLineApp
Указатель на область памяти, которая в случае успешного завершения функции получит хэндл TAPI.
hInstance
Здесь должен помещаться хэндл приложения или библиотеки динамической компоновки (DLL).
lpfnCallback
Указатель на определяемую разработчиком callback функцию, которая будет
обрабатывать сообщения TAPI. Используется при установке такого метода
обработки событий, как «скрытое окно» (“hidden window”). Мы будем
использовать именно такой способ, поэтому ещё рассмотрим эту функцию
более детально.
lpszFriendlyAppName
Указатель на строку (заканчивающуюся нулем). Если значение этого
параметра не равно NULL, оно должно содержать имя приложения. Если
строка содержит NULL, по умолчанию используется имя файла модуля (его
можно получить с помощью Win API функции GetModuleFileName).
lpdwNumDevs
Указатель на память размером в двойное слово (DWORD). В случае
успешного завершения функции в эту память записывается количество
доступных приложению линий (line device).
lpdwAPIVersion
Указатель на память размером DWORD, которая должна содержать версию
(наивысшую), поддержку которой обеспечивает приложение. В нашем случае
это версия 2.2, она будет записана как 00020002h (старшие полбайта
содержат значение «до точки», а младшие, соответственно «после точки»,
так версия 1.3 выглядит как 00010003h).
lpLineInitializeExParams
Указатель на структуру LINEINITIALIZEEXPARAMS, содержащую дополнительные параметры для связывания приложения и TAPI.
Описанная выше функция синхронна, т.е. выполнение функции начинается
тогда, когда она затребована, а завершение происходит при выходе из
функции.
От автора: возврат же из асинхронных функций производится немедленно,
при этом выполнение самой операции продолжается в фоновом режиме.
Выполнение основного потока программы продолжается. При завершении
операции, система сообщает об этом вызвавшему потоку. С такими
функциями мы будем встречаться очень часто.
В использовании lineInitializeEx я думаю всё ясно. Кроме последнего
параметра. Что это за структура LINEINITIALIZEEXPARAMS? Вот она:
typedef struct lineinitializeexparams_tag { DWORD dwTotalSize; DWORD dwNeededSize; DWORD dwUsedSize; DWORD dwOptions; Union { HANDLE hEvent; HANDLE hCompletionPort; } Handles; DWORD dwCompletionKey; } LINEINITIALIZEEXPARAMS, FAR *LPLINEINITIALIZEEXPARAMS;
Первые три параметра структуры определяют соответственно полный размер
структуры в байтах, размер, необходимый для хранения всей возвращаемой
информации, и размер той части структуры, которая содержит полезную
информацию. Такой расклад будет во многих структурах, поэтому стоит
запомнить значения параметров, ибо в дальнейшем я не буду объяснять их
заново. На практике обычно имеет значение лишь значение первого поля.
Дальше идет параметр dwOptions. Он является ключевым для этой
структуры, ибо определяет тот самый механизм обработки событий (или
уведомления о событиях, если быть точнее), о котором говорилось выше.
Этот параметр может принимать следующие значения:
LINEINITIALIZEEXOPTION_CALLHUBTRACKING
Этот механизм обработки используется только в TAPI 3.0 и выше.
LINEINITIALIZEEXOPTION_USECOMPLETIONPORT
Устанавливается механизм обработки событий, называемый Комплексным
(полным, завершенным, не знаю, как будет точнее…) портом (Completion
Port). В этом случае TAPI посылает сообщение приложению, используя
PostQueuedCompletionStatus, посылая тем самым сообщение в Completion
Port. Порт этот можно создать при помощи функции
CreateIoCompletionPort. Приложение получает событие через
GetQueuedCompletionStatus.
LINEINITIALIZEEXOPTION_USEEVENT
В случае использования Хэндла События, TAPI создает объект события на
стороне приложения и возвращает его хэндл. Извлекается сообщение с
помощью lineGetMessage.
LINEINITIALIZEEXOPTION_USEHIDDENWINDOW
При использовании этого механизма TAPI создает скрытое окно, и в
дальнейшем события посылаются процедуре этого окна. Эта процедура –
callback-функция (lineCallbackProc). Этот механизм идентичен механизму,
который используется для обработки сообщений обычных окон. Его я и
использую в своём примере.
Необходимость следующих параметров определяется выбранным механизмом обработки событий.
hEvent
При обработке событий посредством Хэндла события в данное поле TAPI заносит собственно хэндл события :).
hCompletionPort
При использовании Комплексного порта, в это поле нужно поместить его (порта) хэндл.
dwCompletionKey
При том же использовании Комплексного порта, в это поле нужно поместить
значение, возвращаемое GetQueuedCompletionStatus через параметр
lpCompletionKey.
Теперь, немного о callback функции lineCallbackProc...
VOID FAR PASCAL lineCallbackFunc( DWORD hDevice, DWORD dwMsg, DWORD dwCallbackInstance, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3 );
Параметры
hDevice
Здесь должен лежать хэндл линии или звонка, связанного с callback функцией.
dwMsg
Собственно сообщение.
dwCallbackInstance
Это безобразие не интерпретируется TAPI.
Потом идут параметры сообщения.
Я думаю, что пришла пора немного размяться и написать ма-а-аленькую
прогу. Пока она будет уметь только правильно проводить инициализацию.
Назовем её TAPItest.
; TAPItest.inc
include windows.inc ;подключение include user32.inc ;заголовочных include kernel32.inc ;файлов include shell32.inc include comctl32.inc include comdlg32.inc include tapi32.inc include masm32.inc includelib user32.lib ;подключение includelib kernel32.lib ;библиотек includelib shell32.lib includelib comctl32.lib includelib comdlg32.lib includelib tapi32.lib includelib masm32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD ;прототипы WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD lineCallbackFunc PROTO :DWORD, :DWORD,:DWORD,:DWORD,:DWORD,:DWORD .data AppName db 'TAPItest',0 ;имя программы dwHighVer dd 00020002h ;наивысшая поддерживаемая версия dwDeviceID dd 00000000h ;идентификатор девайса szDeviceClass db 'comm/datamodem',0 ;класс девайса .data? hInstance dd ? ;хэндл приложения CommandLine dd ? ;командная строка hWnd dd ? ;хэндл окна hLineApp HLINEAPP ? ;хэндл линии hLine HLINE ? ;хэндл TAPI dwNumDevs dd ? ;количество доступных девайсов
; TAPItest.asm
.386 .model flat,stdcall option casemap:none
include TAPItest.inc
.code start: invoke GetModuleHandle,NULL ;получаем хэндл приложения mov hInstance,eax invoke GetCommandLine ; здесь получаем хэндл командной строки mov CommandLine,eax invoke InitCommonControls invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc \ hInst:HINSTANCE,hPrevInst: HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD ;---стандартная «начинка» WinMain--- WinMain endp
WndProc proc hWin:HWND, uMsg:UINT,wParam:WPARAM,lParam:LPARAM LOCAL LineInitExParam:LINEINITIALIZEEXPARAMS mov LineInitExParam.dwTotalSize, SIZEOF LINEINITIALIZEEXPARAMS mov lineInitExParam.dwOptions, \ LINEINITIALIZEEXOPTION_USEHIDDENWINDOW ;---здесь обработчики разных событий--- ;---Бла-бла-бла------------------------ ;---потом где-то в коде---------------- invoke lineInitializeEx,addr hLineApp, hInstance,addr lineCallbackFunc,addr AppName,\ addr dwNumDevs,addr dwHighVer,addr LineInitExParam .if eax==0 ;---здесь то, о чем мы ещё поговорим--- .endif WndProc endp
lineCallbackFunc proc hDevice:DWORD,dwMsg:DWORD, dwCallbackInstance:DWORD, dwParam1:DWORD,dwParam2:DWORD,dwParam3:DWORD .if dwMsg==LINE_CREATE ;---перехват сообщения (LINE_CREATE)--- .endif ret lineCallbackFunc endp
end start
Да, и ещё... В случае успеха, lineInitializeEx, как почти все TAPI
функции возвращает 0, в случае провала – одно из отрицательных
константных значений LINEERR_constant. Некоторые строки
прокомментированы прямо в листинге, а некоторые я сейчас поясню. Я,
конечно, не стал полностью описывать содержание функций WinMain и
WinProc, ИМХО глупо постоянно повторять одно и то же в разных
туториалах.… В WinProc инициализируем TAPI. Вызов lineInitializeEx
однозначен и, думаю, не вызывает вопросов.
Сообщения TAPI пока перехватывать не будем, поэтому пока я только
показал пример использования callback-функции lineCallbackFunc.
Вот мы и инициализировали Telephony API! По-моему выговорить десять раз
подряд слово «инициализация» и то сложнее, чем инициализировать TAPI.
Первый шаг сделан. Скоро пойдём дальше... |
|