В этот раз будем учиться делать странное, а именно отправлять USSD-запрос оператору мобильной связи с помощью TAPI в Windows Mobile.

USSD (Unstructured Supplementary Service Data) — стандартный сервис в сетях GSM, позволяющий организовать интерактивное взаимодействие между абонентом сети и сервисным приложением в режиме передачи коротких сообщений. – Wikipedia

На самом деле USSD-запросы это то, чем каждый из нас пользуется довольно часто, например, при пополнении счета или при запросе состояния счета у оператора мобильной связи.

Примеры USSD-запросов:

  • *101#
  • *100*111122223333444455#

Вот именно такой финт ушами мы и будем пытаться реализовать с помощью программных средств.

Долгих вступлений не будет, а будет сразу практика с простеньким примером. Рассмотрим простейшее приложение, которое отправляет запрос состояния счета мобильному опреатору.

Для начала нам надо инициализировать TAPI.

#include <windows.h>
#include <tapi.h>
#include <extapi.h>
#include <tsp.h>

#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;
}
&#91;/sourcecode&#93;
И вот, вызов <a href="http://msdn.microsoft.com/en-us/library/ms862581.aspx" title="Отправка USSD запроса">lineSendUSSD</a> - это то к чему мы так стремились. Функция принимает в качестве параметров хэндл телефонной линии, строку запроса и длину запроса. В случае успешного завершения работы возвращается нулевой результат. В случае ошибки возвращается отрицательное значение.

И в качестве дополнения ко всему сказанному выше, класс-обертка для wxWinCE:

<strong>wxUSSDRequest.h</strong>

#ifndef _WX_USSD_REQUEST_H
#define _WX_USSD_REQUEST_H

#include <wx/wx.h>
#include <tapi.h>
#include <extapi.h>
#include <tsp.h>

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);
}

Скачать исходный код примера.

Previous ArticleNext Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

This post has 5 Comments

5
  1. ого, а ведь можно просто в ком порт написать:
    AT+CUSD=”*101#”

  2. Можно, но это будет хак. Какая-то у тебя нездоровая любовь к реализации всего вручную 😛

  3. Вобще на самом деле работа с COM-портом не всегда плохо, но если, например, для решения задачи можно использовать API, предоставляемое ОС, то лучше использовать его.

  4. нет, нет, нет я не отрицаю полезности API, но просто киллограмы кода на то что делается 1-й AT командой…

  5. Эээ.. Ну а если руками отправлять AT-командой, это ж все равно надо писать код проверки, на каком порту висит GPRS-модем, это как минимум. Ну и глядя на то, как MS относится к системе безопасности начиная с WM5, вполне возможно что в какой-нибудь из новых версий для прямого доступа к аппаратным устройствам они будут просить чтобы приложение подписано было.
    Там вроде в Windows Mobile Developer Blog анонс был следующих топиков, обещали вроде тему “Deep Dive: RIL APIs vs TAPI”, может в каментах потом чего интересного расскажут, там часто по существу такие полезные каменты бывают

Leave a Reply

Your email address will not be published. Required fields are marked *

Р.

Работаем с журналом звонков в Windows Mobile

В этот раз я расскажу о том, как работать с журналом звонков на С++ в Windows Mobile.

Для доступа к журналу звонков Windows Mobile имеет такую вещь, как Phone API.

Непосредственно для наших целей необходима всего небольшая часть функций, доступных в рамках Phone API, а именно:

  • PhoneOpenCallLog – открывает журнал звонков для чтения и возвращает хэндл, использующийся впоследствии для доступа к записям журнала.
  • PhoneGetCallLogEntry – получает данные о записи журнала звонков и заполняет структуру CALLLOGENTRY этими данными
  • PhoneCloseCallLog – закрывает хэндл журнала звонков.

Структура CALLLOGENTRY после успешного завершения работы функции PhoneGetCallLogEntryбудет содержать такую информацию:

  • Телефонный номер, на который был совершен звонок (если этот звонок исходящий) или с которого был совершен звонок (если звонок входящий)
  • Имя записи в адресной книге, соответствующей номеру
  • Тип номера (домашний/рабочий/мобильный), берется также из адресной книги
  • Дата и время начала звонка
  • Дата и время окончания звонка
  • Тип звонка (входящий, исходящий, пропущенный)
  • Флаг, указывающий на то, произошло ли соединение
  • Флаг, указывающий на то, был ли звонок завершен нормально или произошел обрыв
  • Флаг, указывающий на то, был ли использован роуминг.
  • Тип контакта (доступен/недоступен/заблокирован)
  • Текст заметки

Итак, давайте посмотрим, как это все работает. Пример для этой статьи написан с использованием wxWinCE. Главная форма приложения содержит list control со списком звонков, в котором указано имя контакта и с какого номера был произведен звонок.

Windows Mobile - Get Call Log Entries - C++ - Main Screen

При запуске приложения выполняется получения данных из журнала звонков:

void wxCallLogSampleMainFrame::FillCallLogList()
{
	m_CallLogListView->Freeze();
	do 
	{
		m_CallLog.Clear();
		HANDLE callLogHandle = INVALID_HANDLE_VALUE;
		HRESULT hr = PhoneOpenCallLog(&callLogHandle);
		if(FAILED(hr)) break;
		CALLLOGENTRY entry;
		entry.cbSize = sizeof(CALLLOGENTRY);
		do 
		{
			hr = PhoneGetCallLogEntry(callLogHandle, &entry);
			if(hr == S_OK)
			{
				wxCallLogEntry result;
				CreateCallLogEntry(entry, result);
				m_CallLog.Add(result);
			}
		} while (hr != S_FALSE);
		PhoneCloseCallLog(callLogHandle);
		m_CallLogListView->SetCallLogArray(&m_CallLog);
		m_CallLogListView->SetItemCount(m_CallLog.Count());
	} while (false);
	m_CallLogListView->Thaw();
}

Для хранения информации о звонке в виде, доступном для использования библиотекой wxWidgets мне пришлось написать собственный класс:

wxCallLogEntry.h

#ifndef _WX_CALL_LOG_ENTRY_H
#define _WX_CALL_LOG_ENTRY_H

#include <wx/wx.h>
#include <wx/dynarray.h>

enum wxCallType
{
	wxCALL_INCOMING,
	wxCALL_OUTGOING,
	wxCALL_MISSED
};

enum wxCallerIDType
{
	wxCALLER_ID_AVAILABLE,
	wxCALLER_ID_UNAVAILABLE,
	wxCALLER_ID_BLOCKED
};

class wxCallLogEntry : public wxObject
{
	DECLARE_DYNAMIC_CLASS(wxCallLogEntry)
public:
	wxCallLogEntry() {}
	wxCallLogEntry(const wxString & phoneNumber,
		const wxDateTime & startTime,
		const wxDateTime & endTime,
		const wxString & callName,
		const wxString & callNameType,
		wxCallType callType,
		wxCallerIDType callerIDType,
		bool wasConnected,
		bool wasEnded,
		bool roamingEnabled,
		const wxString & note = wxEmptyString);

	const wxString & GetPhoneNumber();
	void SetPhoneNumber(const wxString & value);

	const wxDateTime & GetStartTime();
	void SetStartTime(const wxDateTime & value);

	const wxDateTime & GetEndTime();
	void SetEndTime(const wxDateTime & value);

	const wxString & GetCallName();
	void SetCallName(const wxString & value);

	const wxString & GetCallNameType();
	void SetCallNameType(const wxString & value);
	
	wxCallType GetCallType();
	void SetCallType(wxCallType value);

	wxCallerIDType GetCallerIDType();
	void SetCallerIDType(wxCallerIDType value);

	bool GetConnected();
	void SetConnected(bool value);

	bool GetEnded();
	void SetEnded(bool value);

	bool GetRoamingEnabled();
	void SetRoamingEnabled(bool value);

	const wxString & GetNote();
	void SetNote(const wxString & value);
private:
	wxString m_PhoneNumber;
	wxDateTime m_StartTime;
	wxDateTime m_EndTime;
	wxString m_CallName;
	wxString m_CallNameType;
	wxCallType m_CallType;
	wxCallerIDType m_CallerIDType;
	bool m_Connected;
	bool m_Ended;
	bool m_RoamingEnabled;
	wxString m_Note;
};

WX_DECLARE_OBJARRAY(wxCallLogEntry, wxCallLogArray);

#endif

Код для преобразования данных из CALLLOGENTRY в wxCallLogEntry

void wxCallLogSampleMainFrame::CreateCallLogEntry(
	CALLLOGENTRY & entry, wxCallLogEntry & result)
{
	wxCallerIDType idType = wxCALLER_ID_UNAVAILABLE;
	switch(entry.cidt)
	{
	case CALLERIDTYPE_UNAVAILABLE:
		idType = wxCALLER_ID_UNAVAILABLE;
		break;
	case CALLERIDTYPE_BLOCKED:
		idType = wxCALLER_ID_BLOCKED;
		break;
	case CALLERIDTYPE_AVAILABLE:
		idType = wxCALLER_ID_AVAILABLE;
		break;
	default:
		break;
	};
	result.SetCallerIDType(idType);

	wxCallType callType = wxCALL_INCOMING;
	switch(entry.iom)
	{
	case IOM_INCOMING:
		callType = wxCALL_INCOMING;
		break;
	case IOM_OUTGOING:
		callType = wxCALL_OUTGOING;
		break;
	case IOM_MISSED:
		callType = wxCALL_MISSED;
		break;
	default:
		break;
	};
	result.SetCallType(callType);

	SYSTEMTIME systemTime;

	FileTimeToSystemTime(&entry.ftStartTime, &systemTime);
	wxDateTime startTime(TimeFromSystemTime(&systemTime));
	result.SetStartTime(startTime);
	
	FileTimeToSystemTime(&entry.ftEndTime, &systemTime);
	wxDateTime endTime(TimeFromSystemTime(&systemTime));
	result.SetEndTime(endTime);
	
	wxString number = ((entry.pszNumber != NULL) ? 
		wxString::Format(wxT("%s"),entry.pszNumber) :
	wxEmptyString);
	result.SetPhoneNumber(number);
	
	wxString name = ((entry.pszName != NULL) ? 
		wxString::Format(wxT("%s"),entry.pszName) :
	wxEmptyString);
	result.SetCallName(name);
	
	wxString nameType = ((entry.pszNameType != NULL) ? 
		wxString::Format(wxT("%s"),entry.pszNameType) :
	wxEmptyString);
	result.SetCallNameType(nameType);

	wxString note = ((entry.pszNote != NULL) ? 
		wxString::Format(wxT("%s"),entry.pszNote) :
	wxEmptyString);
	result.SetNote(note);

	result.SetConnected(entry.fConnected != 0);
	result.SetEnded(entry.fEnded != 0);
	result.SetRoamingEnabled(entry.fRoam != 0);
}

Структура CALLOGENTRY хранит дату и время начала и окончания звонка в виде структуры FILETIME. Для преобразования FILETIME в wxDateTime сначала необходимо выполнить преобразование в SYSTEMTIME с помощью функции FileTimeToSystemTime(), а затем в time_t:

time_t TimeFromSystemTime(const SYSTEMTIME * pTime)
{
	tm _tm;
	memset(&_tm, 0, sizeof(tm));

	_tm.tm_year = (pTime->wYear-1900);
	_tm.tm_mon = pTime->wMonth - 1;
	_tm.tm_mday = pTime->wDay;

	_tm.tm_hour = pTime->wHour;
	_tm.tm_min = pTime->wMinute;
	_tm.tm_sec = pTime->wSecond;

	return mktime(&_tm);
}

Для отображения данных о звонках я решил использовать виртуальный list control. Почему виртуальный, а не обычный? Потому что добавление большого количества элементов в список происходит довольно долго и пользователь может несколько секунд ждать того момента, когда все данные будут добавлены и приложение начнет как-то реагировать на его действия. При использовании виртуального списка вызывается метод SetItemCount(), который позволяет правильно вычислить размеры скроллеров. Это происходит довльно быстро. Затем виртуальный list control отображает только видимые элементы списка.

Для создания виртуального list control’а необходимо создать класс производный от wxListCtrl или wxListView и переопределить в нем методы:

virtual wxString OnGetItemText(long item, long column) const;
virtual int OnGetItemImage(long item) const;
virtual wxListItemAttr * OnGetItemAttr(long item) const;
  • OnGetItemText – используется для получения текста ячейки списка по указанным номеру строки и номеру колонки
  • OnGetItemImage – используется для получения индекса картинки элемента списка по указанному номеру строки
  • OnGetItemAttr – используется для получения атрибутов элемента списка (цвет текста, цвет фона, шрифт) по указанному номеру строки

И вот, собственно, код этих трех методов

wxString wxCallLogListView::OnGetItemText(long item, long column) const
{
	do 
	{
		if(!m_CallLogArray || 
			item >= (long)m_CallLogArray->Count() || 
			item >= GetItemCount()) break;
		switch(column)
		{
		case 2:
			return m_CallLogArray->Item(item).GetPhoneNumber();
		case 0:
			return m_CallLogArray->Item(item).GetCallName();
		case 1:
			return m_CallLogArray->Item(item).GetCallNameType();
		default:
			break;
		}
	} while (false);
	return wxEmptyString;
}

int wxCallLogListView::OnGetItemImage(long item) const
{
	do 
	{
		if(!m_CallLogArray || 
			item >= (long)m_CallLogArray->Count() || 
			item >= GetItemCount()) break;
		switch(m_CallLogArray->Item(item).GetCallType())
		{
		case wxCALL_INCOMING:
			return 1;
		case wxCALL_OUTGOING:
			return 2;
		case wxCALL_MISSED:
			return 3;
		}
	} while (false);
	return -1;
}

wxListItemAttr * wxCallLogListView::OnGetItemAttr(long item) const
{
	do 
	{
		if(!m_CallLogArray || 
			item >= (long)m_CallLogArray->Count() || 
			item >= GetItemCount()) break;
		return new wxListItemAttr(*wxBLACK, 
			(item%2) ? *wxWHITE : *wxLIGHT_GREY,
			GetFont());
	} while (false);
	return NULL;
}

Для того, чтобы в списке отображались иконки необходимо сначала создать объект wxImageList, добавить в него иконки и затем ассоциировать с list control’ом с помощью метода SetImageList()

static bool imageListCreated = false;
if(!imageListCreated)
{
	wxCallLogListView::ItemImageList.Create(16, 15);
	wxBitmap empty(16, 15);
	empty.SetMask(new wxMask(empty, *wxBLACK));
	wxCallLogListView::ItemImageList.Add(empty);
	wxCallLogListView::ItemImageList.Add(wxBitmap(forward_xpm));
	wxCallLogListView::ItemImageList.Add(wxBitmap(back_xpm));
	wxCallLogListView::ItemImageList.Add(wxBitmap(delete_xpm));
	imageListCreated = true;
}

Сейчас наше тестовое приложение отображает информацию о звонках не полностью. Для получения полной информации о звонке создадим диалоговое окно и при выделении элемента списка будем заполнять его данными и отображать.

BEGIN_EVENT_TABLE( wxCallLogSampleMainFrame, wxFrame )
    EVT_LIST_ITEM_ACTIVATED( ID_CallLogListView, wxCallLogSampleMainFrame::OnCallLogListViewItemActivated )
END_EVENT_TABLE()
void wxCallLogSampleMainFrame::OnCallLogListViewItemActivated( wxListEvent& event )
{
	CallInfoDialog * dlg = new CallInfoDialog(this);
	dlg->SetCallLogEntry(&m_CallLog[event.GetSelection()]);
	dlg->ShowModal();
	dlg->Destroy();
}

void CallInfoDialog::SetCallLogEntry(wxCallLogEntry * entry)
{
	if(!entry) return;
	m_PhoneNumberTextCtrl->SetValue(entry->GetPhoneNumber());
	m_CallNameTextCtrl->SetValue(entry->GetCallName());
	m_CallNameTypeTextCtrl->SetValue(entry->GetCallNameType());
	m_StartTextCtrl->SetValue(entry->GetStartTime().Format());
	m_EndTextCtrl->SetValue(entry->GetEndTime().Format());
	m_CallTypeRadio->SetSelection(entry->GetCallType());
	m_CallerTypeRadio->SetSelection(entry->GetCallerIDType());
	m_ConnectedCheck->SetValue(entry->GetConnected());
	m_EndedCheck->SetValue(entry->GetEnded());
	m_RoamingCheck->SetValue(entry->GetRoamingEnabled());
	m_NotesTextCtrl->SetValue(entry->GetNote());
}

В результате у нас должно получиться что-то подобное:
Windows Mobile - Show Call Log Entries - C++ - Show Details

Скачать исходный код к статье + проект для Windows Mobile 5.

Д.

Доступна регистрация в Windows Marketplace for Mobile

И вот, ура! Свершилось наконец-то. Microsoft открыла регистрацию в Windows Marketplace for Mobile и разработчики, которые пишут для Windows Mobile могут уже начать работать с этой площадкой продажи мобильных приложений. Вот уже смотрю на условия участия… Годовая подписка стоит эээ.. 67 фунтов. Как-то… немало. По сравнению с 25 долларами для Android Marker, цена довольно высокая.