Отправка USSD-запроса в Windows Mobile
В этот раз будем учиться делать странное, а именно отправлять USSD-запрос оператору мобильной связи с помощью TAPI в Windows Mobile.
USSD (Unstructured Supplementary Service Data) — стандартный сервис в сетях GSM, позволяющий организовать интерактивное взаимодействие между абонентом сети и сервисным приложением в режиме передачи коротких сообщений. – Wikipedia
На самом деле USSD-запросы это то, чем каждый из нас пользуется довольно часто, например, при пополнении счета или при запросе состояния счета у оператора мобильной связи.
Примеры USSD-запросов:
- *101#
- *100*111122223333444455#
Вот именно такой финт ушами мы и будем пытаться реализовать с помощью программных средств.
Долгих вступлений не будет, а будет сразу практика с простеньким примером. Рассмотрим простейшее приложение, которое отправляет запрос состояния счета мобильному опреатору.
Для начала нам надо инициализировать TAPI.
#include#include #include #include #define EXT_API_LOW_VERSION 0x00010000 #define EXT_API_HIGH_VERSION 0x00010000 long InitializeTAPI(HLINEAPP & lineApp, DWORD & lowAPIVersion, DWORD & deviceCount) { LINEINITIALIZEEXPARAMS lineParams; ZeroMemory(&lineParams,sizeof(LINEINITIALIZEEXPARAMS)); lineParams.dwTotalSize = sizeof(LINEINITIALIZEEXPARAMS); lineParams.dwOptions = LINEINITIALIZEEXOPTION_USEHIDDENWINDOW; lowAPIVersion = TAPI_CURRENT_VERSION; return lineInitializeEx(&lineApp, (HINSTANCE)GetModuleHandle(NULL), LineCallback, L"wxUSSDRequest", &deviceCount, &lowAPIVersion, &lineParams); }
LineCallback – это callback-функция, которая будет вызываться при наступлении событий TAPI.
void FAR PASCAL LineCallback(DWORD hDevice, DWORD dwMsg, DWORD dwCallbackInstance, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3) { switch(dwMsg) { case LINE_DEVSPECIFIC: { if(dwParam1 == LINE_USSD) { DWORD dwMessageId = dwParam2; } break; } case LINE_REPLY: { if(dwParam2 == 0) return; wprintf(L"Error: "); switch(dwParam2) { case LINEERR_INVALLINEHANDLE: wprintf(L"LINEERR_INVALLINEHANDLE"); break; case LINEERR_NOMEM: wprintf(L"LINEERR_NOMEM"); break; case LINEERR_OPERATIONUNAVAIL: wprintf(L"LINEERR_OPERATIONUNAVAIL"); break; case LINEERR_OPERATIONFAILED: wprintf(L"LINEERR_OPERATIONFAILED"); break; case LINEERR_RESOURCEUNAVAIL: wprintf(L"LINEERR_RESOURCEUNAVAIL"); break; case LINEERR_INVALPOINTER: wprintf(L"LINEERR_INVALPOINTER"); break; case LINEERR_INVALPARAM: wprintf(L"LINEERR_INVALPARAM"); break; case LINEERR_UNINITIALIZED: wprintf(L"LINEERR_UNINITIALIZED"); break; default: wprintf(L"Error: %x",dwParam2); break; } wprintf(L"\r\n"); break; } } }
Затем нужно получить идентификатор телефонной линии. Сделать это можно с помощью функции lineGetDevCaps.
long GetCellularLineID(HLINEAPP lineApp, DWORD lowAPIVersion, DWORD deviceCount, DWORD & apiVersion) { DWORD dwReturn = 0xFFFFFFFF; long lResult = 0; LINEEXTENSIONID sLineExt = {0}; LPLINEDEVCAPS lpLineDevCaps = NULL; BOOL bContinue = TRUE; for(DWORD dwLine = 0; dwLine < deviceCount && bContinue; ++dwLine) { lResult = lineNegotiateAPIVersion(lineApp, dwLine, lowAPIVersion, TAPI_CURRENT_VERSION, &apiVersion, &sLineExt); if(0 == lResult) { lpLineDevCaps = (LPLINEDEVCAPS)LocalAlloc(LPTR,sizeof(LINEDEVCAPS)); lResult = LINEERR_STRUCTURETOOSMALL; lpLineDevCaps->dwTotalSize = sizeof(LINEDEVCAPS); lpLineDevCaps->dwNeededSize = sizeof(LINEDEVCAPS); while(LINEERR_STRUCTURETOOSMALL == lResult) { lResult = lineGetDevCaps(lineApp,dwLine,TAPI_CURRENT_VERSION,0,lpLineDevCaps); if(LINEERR_STRUCTURETOOSMALL == lResult || lpLineDevCaps->dwTotalSize < lpLineDevCaps->dwNeededSize) { lpLineDevCaps = (LPLINEDEVCAPS)LocalReAlloc(lpLineDevCaps,lpLineDevCaps->dwNeededSize,LMEM_MOVEABLE); lResult = LINEERR_STRUCTURETOOSMALL; lpLineDevCaps->dwTotalSize = lpLineDevCaps->dwNeededSize; } } if(0 == lResult) { TCHAR szName[512]; memcpy((PVOID)szName,(PVOID)((BYTE*)lpLineDevCaps + lpLineDevCaps ->dwLineNameOffset), lpLineDevCaps->dwLineNameSize); szName[lpLineDevCaps->dwLineNameSize] = 0; if(_tcscmp(szName,CELLTSP_LINENAME_STRING) == 0) { dwReturn = dwLine; bContinue = FALSE; } } LocalFree((HLOCAL)lpLineDevCaps); } } return dwReturn; }
Теперь нужно открыть телефонную линию для последующей работы с ней. Делается это с помощью функции lineOpen.
HLINE OpenTAPILine(HLINEAPP lineApp, DWORD cellularID, DWORD apiVersion) { DWORD dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE; HLINE hLine = NULL; DWORD extVersion = 0; long lReturn = lineOpen(lineApp, cellularID, &hLine, TAPI_CURRENT_VERSION, 0, (DWORD)NULL, LINECALLPRIVILEGE_OWNER, dwMediaMode, 0); lReturn = ::lineNegotiateExtVersion(lineApp, cellularID, apiVersion, EXT_API_LOW_VERSION, EXT_API_HIGH_VERSION, &extVersion); return hLine; }
После завершения работы с TAPI необходимо обязательно закрыть все хэндлы.
void ShutdownTAPI(HLINE cellularLine, HLINEAPP lineApp) { if(cellularLine) { lineClose(cellularLine); } if(lineApp) { lineShutdown(lineApp); } }
И вот мы добрались до самого интересного, а именно до того момента, когда нам все предыдущие части кода нужно использовать для достижения конечной цели – зохвата мира отправки запроса.
int wmain() { HLINEAPP lineApp = NULL; DWORD lowAPIVersion = 0; DWORD apiVersion = 0; DWORD deviceCount = 0; DWORD cellularID = 0; HLINE cellularLine = NULL; TCHAR command[] = _T("*101#"); int errorCode; do { if(InitializeTAPI(lineApp, lowAPIVersion, deviceCount) != 0) { errorCode = 1; break; } cellularID = GetCellularLineID(lineApp, lowAPIVersion, deviceCount, apiVersion); if(cellularID == 0xFFFFFFFF) { errorCode = 2; break; } cellularLine = OpenTAPILine(lineApp, cellularID, apiVersion); if(cellularLine == NULL) { errorCode = 3; break; } // Супер-строчка, которая отправляет USSD-запрос !!! Уиииии! if(lineSendUSSD(cellularLine, (const BYTE* const)command, sizeof(command), 0) < 0) { errorCode = 4; break; } return 0; } while(false); ShutdownTAPI(cellularLine, lineApp); return errorCode; }
И вот, вызов lineSendUSSD – это то к чему мы так стремились. Функция принимает в качестве параметров хэндл телефонной линии, строку запроса и длину запроса. В случае успешного завершения работы возвращается нулевой результат. В случае ошибки возвращается отрицательное значение.
И в качестве дополнения ко всему сказанному выше, класс-обертка для wxWinCE:
wxUSSDRequest.h
#ifndef _WX_USSD_REQUEST_H #define _WX_USSD_REQUEST_H #include#include #include #include class wxUSSDRequest { long InitializeTAPI(DWORD & lowAPIVersion, DWORD & deviceCount); long GetCellularLineID(DWORD lowAPIVersion, DWORD deviceCount, DWORD & apiVersion); HLINE OpenTAPILine(DWORD cellularID, DWORD apiVersion); void ShutdownTAPI(); static void FAR PASCAL LineCallback(DWORD hDevice, DWORD dwMsg, DWORD dwCallbackInstance, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3); public: wxUSSDRequest(); ~wxUSSDRequest(); bool SendUSSDCommand(const TCHAR * command, DWORD length); int Init(); bool IsOK(); private: HLINEAPP m_LineApp; HLINE m_CellularLine; bool m_IsOK; }; #endif
wxUSSDRequest.cpp
#include "wxUSSDRequest.h" #define EXT_API_LOW_VERSION 0x00010000 #define EXT_API_HIGH_VERSION 0x00010000 wxUSSDRequest::wxUSSDRequest() : m_LineApp(NULL), m_CellularLine(NULL), m_IsOK(false) { m_IsOK = (Init() == 0); } wxUSSDRequest::~wxUSSDRequest() { ShutdownTAPI(); } int wxUSSDRequest::Init() { int result(0); do { DWORD lowAPIVersion(0); DWORD deviceCount(0); if(InitializeTAPI(lowAPIVersion, deviceCount) != 0) { result = 1; break; } DWORD apiVersion(0); DWORD cellularID = GetCellularLineID(lowAPIVersion, deviceCount, apiVersion); if(cellularID == 0xFFFFFFFF) { result = 2; break; } m_CellularLine = OpenTAPILine(cellularID, apiVersion); if(m_CellularLine == NULL) { result = 3; break; } } while(false); return result; } bool wxUSSDRequest::IsOK() { return m_IsOK; } bool wxUSSDRequest::SendUSSDCommand(const TCHAR * command, DWORD length) { if(lineSendUSSD(m_CellularLine, (const BYTE* const)command, length, 0) < 0) { return false; } return true; } long wxUSSDRequest::InitializeTAPI(DWORD & lowAPIVersion, DWORD & deviceCount) { LINEINITIALIZEEXPARAMS lineParams; ZeroMemory(&lineParams,sizeof(LINEINITIALIZEEXPARAMS)); lineParams.dwTotalSize = sizeof(LINEINITIALIZEEXPARAMS); lineParams.dwOptions = LINEINITIALIZEEXOPTION_USEHIDDENWINDOW; lowAPIVersion = TAPI_CURRENT_VERSION; return lineInitializeEx(&m_LineApp, (HINSTANCE)wxTheApp->GetInstance(), LineCallback, wxT("wxUSSDRequest"), &deviceCount, &lowAPIVersion, &lineParams); } void wxUSSDRequest::ShutdownTAPI() { if(m_CellularLine) { lineClose(m_CellularLine); } if(m_LineApp) { lineShutdown(m_LineApp); } m_LineApp = NULL; m_CellularLine = NULL; } long wxUSSDRequest::GetCellularLineID(DWORD lowAPIVersion, DWORD deviceCount, DWORD & apiVersion) { DWORD dwReturn = 0xFFFFFFFF; long lResult = 0; LINEEXTENSIONID sLineExt = {0}; LPLINEDEVCAPS lpLineDevCaps = NULL; BOOL bContinue = TRUE; for(DWORD dwLine = 0; dwLine < deviceCount && bContinue; ++dwLine) { lResult = lineNegotiateAPIVersion(m_LineApp, dwLine, lowAPIVersion, TAPI_CURRENT_VERSION, &apiVersion, &sLineExt); if(0 == lResult) { lpLineDevCaps = (LPLINEDEVCAPS)LocalAlloc(LPTR,sizeof(LINEDEVCAPS)); lResult = LINEERR_STRUCTURETOOSMALL; lpLineDevCaps->dwTotalSize = sizeof(LINEDEVCAPS); lpLineDevCaps->dwNeededSize = sizeof(LINEDEVCAPS); while(LINEERR_STRUCTURETOOSMALL == lResult) { lResult = lineGetDevCaps(m_LineApp,dwLine,TAPI_CURRENT_VERSION,0,lpLineDevCaps); if(LINEERR_STRUCTURETOOSMALL == lResult || lpLineDevCaps->dwTotalSize < lpLineDevCaps->dwNeededSize) { lpLineDevCaps = (LPLINEDEVCAPS)LocalReAlloc(lpLineDevCaps,lpLineDevCaps->dwNeededSize,LMEM_MOVEABLE); lResult = LINEERR_STRUCTURETOOSMALL; lpLineDevCaps->dwTotalSize = lpLineDevCaps->dwNeededSize; } } if(0 == lResult) { TCHAR szName[512]; memcpy((PVOID)szName,(PVOID)((BYTE*)lpLineDevCaps + lpLineDevCaps ->dwLineNameOffset), lpLineDevCaps->dwLineNameSize); szName[lpLineDevCaps->dwLineNameSize] = 0; if(_tcscmp(szName,CELLTSP_LINENAME_STRING) == 0) { dwReturn = dwLine; bContinue = FALSE; } } LocalFree((HLOCAL)lpLineDevCaps); } } return dwReturn; } HLINE wxUSSDRequest::OpenTAPILine(DWORD cellularID, DWORD apiVersion) { DWORD dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE; HLINE hLine = NULL; DWORD extVersion = 0; long lReturn = lineOpen(m_LineApp, cellularID, &hLine, TAPI_CURRENT_VERSION, 0, (DWORD)this, LINECALLPRIVILEGE_OWNER, dwMediaMode, 0); lReturn = ::lineNegotiateExtVersion(m_LineApp, cellularID, apiVersion, EXT_API_LOW_VERSION, EXT_API_HIGH_VERSION, &extVersion); return hLine; } void FAR PASCAL wxUSSDRequest::LineCallback(DWORD hDevice, DWORD dwMsg, DWORD dwCallbackInstance, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3) { switch(dwMsg) { case LINE_DEVSPECIFIC: { if(dwParam1 == LINE_USSD) { DWORD dwMessageId = dwParam2; } break; } case LINE_REPLY: { wxString strError; switch(dwParam2) { case 0: strError = wxEmptyString; break; case LINEERR_INVALLINEHANDLE: strError = wxT("LINEERR_INVALLINEHANDLE"); break; case LINEERR_NOMEM: strError = wxT("LINEERR_NOMEM"); break; case LINEERR_OPERATIONUNAVAIL: strError = wxT("LINEERR_OPERATIONUNAVAIL"); break; case LINEERR_OPERATIONFAILED: strError = wxT("LINEERR_OPERATIONFAILED"); break; case LINEERR_RESOURCEUNAVAIL: strError = wxT("LINEERR_RESOURCEUNAVAIL"); break; case LINEERR_INVALPOINTER: strError = wxT("LINEERR_INVALPOINTER"); break; case LINEERR_INVALPARAM: strError = wxT("LINEERR_INVALPARAM"); break; case LINEERR_UNINITIALIZED: strError = wxT("LINEERR_UNINITIALIZED"); break; default: strError.Format(_("Error: %x"),dwParam2); break; } if(!strError.IsEmpty()) { wxLogError(strError); } break; } } }
Пример использования
wxUSSDRequest * m_USSDRequest; ... wxUSSDRequest * GetUSSDRequest() const { return m_USSDRequest ; } ... void wxUSSDRequestMainFrame::OnSENDClick( wxCommandEvent& event ) { do { if(!wxGetApp().GetUSSDRequest() || !wxGetApp().GetUSSDRequest()->IsOK()) break; wxString command = m_USSDTextCtrl->GetValue().GetData(); if(!wxGetApp().GetUSSDRequest()->SendUSSDCommand(command, command.Length() * sizeof(wxChar))) { wxLogError(_("Error sending USSD message '%s'"), m_USSDTextCtrl->GetValue().GetData()); } } while(false); }
Скачать исходный код примера.
Еще интересные посты о программировании для мобильных устройств:
5 Comments
Make A CommentComments RSS Feed TrackBack URL
February 14th, 2009 at 14:21
ого, а ведь можно просто в ком порт написать:
AT+CUSD=”*101#”
February 14th, 2009 at 15:01
Можно, но это будет хак. Какая-то у тебя нездоровая любовь к реализации всего вручную
February 14th, 2009 at 15:20
Вобще на самом деле работа с COM-портом не всегда плохо, но если, например, для решения задачи можно использовать API, предоставляемое ОС, то лучше использовать его.
February 14th, 2009 at 20:31
нет, нет, нет я не отрицаю полезности API, но просто киллограмы кода на то что делается 1-й AT командой…
February 15th, 2009 at 00:42
Эээ.. Ну а если руками отправлять AT-командой, это ж все равно надо писать код проверки, на каком порту висит GPRS-модем, это как минимум. Ну и глядя на то, как MS относится к системе безопасности начиная с WM5, вполне возможно что в какой-нибудь из новых версий для прямого доступа к аппаратным устройствам они будут просить чтобы приложение подписано было.
Там вроде в Windows Mobile Developer Blog анонс был следующих топиков, обещали вроде тему “Deep Dive: RIL APIs vs TAPI”, может в каментах потом чего интересного расскажут, там часто по существу такие полезные каменты бывают