В этот раз я расскажу о том, как работать с журналом звонков на С++ в 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.

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

Leave a Reply

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

К.

Как создать фигурное окошко в Windows Mobile

И вот еще один небольшой пример, демонстрирующий создание окна непрямоугольной формы в Windows Mobile с библиотекой wxWinCE.

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

Ну а затем нужно выполнить вот такой финт ушами:

  • Создать изображение (черно-белое)
  • Создать для него контекст устройства
  • Нарисовать что-либо (черные пикселы станут прозрачными, белые – видимыми)
  • Создать регион из изображения (wxRegion)
  • Указать форме регион для отображения
void wxMobileTransparencyMainFrame::ChangeShape()
{
	int width(0), height(0);
	// Получаем размер окна
	GetClientSize(&width, &height);
	// Создаем изображение
	wxBitmap bitmap(width, height);
	// Создаем Device Context для изображения
	wxMemoryDC mdc(bitmap);
	// Заполняем черным цветом
	mdc.SetBackground(*wxBLACK_BRUSH);
	mdc.Clear();
	// Устанавливаем кисть белого цвета
	mdc.SetPen(*wxWHITE_PEN);
	wxPoint center(width/2, height/2);
	int radius = wxMin(width, height)/2;
	// Рисуем круг в центре
	mdc.DrawCircle(center, radius);
	// Устанавливаем кисть черного цвета
	mdc.SetPen(*wxBLACK_PEN);
	mdc.SetBrush(*wxBLACK_BRUSH);
	// Рисуем
	mdc.DrawCircle(center.x - radius/3, center.y-radius/4, radius/6);
	mdc.DrawCircle(center.x + radius/3, center.y-radius/4, radius/6);
	mdc.DrawEllipticArc(center.x-radius/3, center.y+radius/4, 
		2 * radius / 3, radius/2, 
		180, 360);
	// Освобождаем Device Context
	mdc.SelectObject(wxNullBitmap);
	// Создаем новый регион
	m_Region = new wxRegion(bitmap, *wxBLACK);
#if defined(__WXWINCE__)
	// Для wxWinCE метод SetRegion() ничего не делает, просто возвращает false.
	// Поэтому приходится устанавливать регион вручную
	HRGN hRgn = (HRGN)m_Region->GetHRGN();
	::SetWindowRgn((HWND)GetHWND(), hRgn, FALSE);
#else
	int offset = GetSize().GetHeight()-GetClientSize().GetHeight();
	m_Region->Offset(0, offset);
	// Устанавливаем регион
	SetShape(*m_Region);
#endif
}

Создаем окно непрямоугольной формы в Windows Mobile
Скачать исходник: Создаем окно непрямоугольной формы в Windows Mobile

П.

Программная отправка SMS на C++ с помощью CE MAPI

Появилась необходимость программно отправлять SMS с телефона под управлением Windows Mobile. Решил не изобретать велосипед и поиспользовать MAPI для этих целей. После недолгих поисков набрел на эту статью на CodeProject. Немного переделал код для использования с wxWidgets. Вот что получилось:

wxSendSMS.h

#ifndef _WX_SMS_SENDER_H
#define _WX_SMS_SENDER_H

#include <wx/wx.h>
#include <atlbase.h>
#include <cemapi.h>
#include <mapiutil.h>

class wxSMSSender
{
public:
	virtual bool Send(const wxString & from, 
		const wxString & to, const wxString & text) = 0;
};

class wxSMSSenderMAPI : public wxSMSSender
{
	HRESULT GetSMSFolder(const CComPtr<IMsgStore>& msgStore, 
		CComPtr<IMAPIFolder>& folder);
	HRESULT GetSMSMsgStore(const CComPtr<IMAPISession>& session, 
		CComPtr<IMsgStore>& msgStore);
	HRESULT SendSMSMessage(const CComPtr<IMAPISession>& session, 
		LPCTSTR lpszFrom, LPCTSTR lpszTo, LPCTSTR lpszMessage);
public:
	virtual bool Send(const wxString & from, 
		const wxString & to, const wxString & text);
};

#endif

wxSendSMS.cpp

#include "wxSMSSender.h"

HRESULT wxSMSSenderMAPI::GetSMSFolder(
			const CComPtr<IMsgStore>& msgStore, 
			CComPtr<IMAPIFolder>& folder)
{
	// Now get the Drafts folder.
	SPropTagArray propDefaultFolder;
	propDefaultFolder.cValues = 1;
	propDefaultFolder.aulPropTag[0] = PR_CE_IPM_DRAFTS_ENTRYID;

	ULONG	values;
	LPSPropValue propVals;
	HRESULT hr = msgStore->GetProps(&propDefaultFolder, 
		MAPI_UNICODE, &values, &propVals);
	if (FAILED(hr))
	{
		return hr;
	}

	SBinary& eidDrafts = propVals->Value.bin;

	hr = msgStore->OpenEntry(eidDrafts.cb, (LPENTRYID)eidDrafts.lpb, 
		NULL, MAPI_MODIFY, NULL, (LPUNKNOWN*)&folder);
	return hr;
}

HRESULT wxSMSSenderMAPI::GetSMSMsgStore(
			const CComPtr<IMAPISession>& session, 
			CComPtr<IMsgStore>& msgStore)
{
	// first we get the msgstores table from the session
	CComPtr<IMAPITable> table;
	HRESULT hr = session->GetMsgStoresTable(MAPI_UNICODE, &table);
	if (FAILED(hr))
	{
		return FALSE;
	}

	// next we loop over the message stores opening each msgstore and
	// getting its name to see if the name matches SMS.
	// If it does then we break out of the loop
	while (TRUE)
	{
		SRowSet* pRowSet = NULL;
		hr = table->QueryRows(1, 0, &pRowSet);

		// If we failed to query the
		// rows then we need to break
		if (FAILED(hr)) break;
		
		// if we got no rows back then just exit the loop
		//remembering to set an error
		if (pRowSet->cRows == 1)
		{
			ASSERT(pRowSet->aRow[0].lpProps->ulPropTag == PR_ENTRYID);
			SBinary& blob = pRowSet->aRow[0].lpProps->Value.bin;
			hr = session->OpenMsgStore(NULL, blob.cb, 
				(LPENTRYID)blob.lpb, NULL, 0, &msgStore);
		}
		else
		{
			hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
		}

		// now remember to free the row set
		FreeProws(pRowSet);
		if (FAILED(hr)) break;

		// now get the display name property from the
		// message store to compare it against the name
		// 'SMS'
		SPropTagArray props;
		props.cValues = 1;
		props.aulPropTag[0] = PR_DISPLAY_NAME;

		ULONG values;
		SPropValue* pProps = NULL;
		hr = msgStore->GetProps(&props, MAPI_UNICODE, &values, &pProps);
		if (FAILED(hr) || values != 1)
		{
			break;
		}

		// if the name matches SMS then break and as
		// hr == S_OK the current MsgStore smart pointer
		// will correctly be set.
		if (_tcsicmp(pProps[0].Value.lpszW, _T("SMS")) == 0)
		{
			break;
		}
	}

	// if we failed for some reason then we clear out
	// the msgstore smartpointer and return the error.
	if (FAILED(hr))
	{
		msgStore.Release();
	}

	return hr;
}

HRESULT wxSMSSenderMAPI::SendSMSMessage(const CComPtr<IMAPISession>& session, 
			LPCTSTR lpszFrom, LPCTSTR lpszTo, LPCTSTR lpszMessage)
{

	// now get the SMS message store
	CComPtr<IMsgStore> msgStore;
	HRESULT hr = GetSMSMsgStore(session, msgStore);
	if (FAILED(hr)) return hr;
	
	CComPtr<IMAPIFolder> folder;
	hr = GetSMSFolder(msgStore, folder);
	if (FAILED(hr)) return hr;

	CComPtr<IMessage> message;
	hr = folder->CreateMessage(NULL, 0 ,&message);
	if (FAILED(hr)) return hr;

	// set the recipients
	// set up the required fields for a recipient
	SPropValue propRecipient[3];
	// it is vital we clear the property structure
	// as there are fields we do not use but MAPI seems
	// to be sentative to them.
	ZeroMemory(&propRecipient, sizeof(propRecipient));
	// set the recipient type which coul be to, cc, bcc
	// but ehre must at least be a to field
	propRecipient[0].ulPropTag = PR_RECIPIENT_TYPE;
	propRecipient[0].Value.l = MAPI_TO;

	// we set the type of address to sms instead of
	// smtp
	propRecipient[1].ulPropTag = PR_ADDRTYPE;
	propRecipient[1].Value.lpszW = _T("SMS");
	// we finally set the email address to the
	// phone number of the person we are sending the message
	// to
	propRecipient[2].ulPropTag = PR_EMAIL_ADDRESS;
	propRecipient[2].Value.lpszW = (LPWSTR)lpszTo;

	// set the addrlist to point to the properties
	ADRLIST adrlist;
	adrlist.cEntries = 1;
	adrlist.aEntries[0].cValues = 3;
	adrlist.aEntries[0].rgPropVals = (LPSPropValue)(&propRecipient);

	// finally modify the recipients of the message
	hr = message->ModifyRecipients(MODRECIP_ADD, &adrlist); 
	if (FAILED(hr)) return hr;

	// now we set the additional properties for the 
	// message
	SPropValue props[4];

	//note how we zero out the contents of the
	// structure as MAPI is sensative to the
	// contents of other fields we do not use.
	ZeroMemory(&props, sizeof(props));

	// first set the subject of the message
	// as the sms we are going to send
	props[0].ulPropTag = PR_SUBJECT;
	props[0].Value.lpszW = (LPWSTR)lpszMessage;

	// next set the senders email address to
	// the phone number of the person we are
	// sending the message to
	props[1].ulPropTag = PR_SENDER_EMAIL_ADDRESS;
	props[1].Value.lpszW = (LPWSTR)lpszFrom;

	// finally and most importantly tell mapi
	// this is a sms message in need of delivery
	props[2].ulPropTag = PR_MSG_STATUS;
	props[2].Value.ul = MSGSTATUS_RECTYPE_SMS;

    props[3].ulPropTag = PR_MESSAGE_FLAGS;
    props[3].Value.ul = MSGFLAG_FROMME | MSGFLAG_UNSENT;

	hr = message->SetProps(sizeof(props) / sizeof(SPropValue), 
		(LPSPropValue)&props, NULL);
	if (FAILED(hr)) return hr;

	// having set all the required fields we can now
	// pass the message over to the msgstore transport
	// to be delivered.
	hr = message->SubmitMessage(0);
	if (FAILED(hr)) return hr;

	return FALSE;
}

bool wxSMSSenderMAPI::Send(const wxString & from, 
		const wxString & to, const wxString & text)
{
	do
	{
		HRESULT hr = MAPIInitialize(NULL);
		if (FAILED(hr)) break;
		CComPtr<IMAPISession> mapiSession;
		hr = MAPILogonEx(0 ,NULL, NULL, 0, &mapiSession);
		if (FAILED(hr)) break;
		bool result = SUCCEEDED(SendSMSMessage(mapiSession, 
			from.GetData(), to.GetData(), text.GetData()));
		mapiSession->Logoff(0, 0, 0);
		mapiSession.Release();
		MAPIUninitialize();
		return result;
	}
	while(false);
	return false;
}

А пользоваться этим всем очень просто:

wxSMSSenderMAPI sender;
if(!sender.Send(m_FromTextCtrl->GetValue(), 
	m_ToTextCtrl->GetValue(), m_SMSTextCtrl->GetValue()))
{
	wxMessageBox(_("Не могу отправить SMS!"));
}

Исходный код примера отправки SMS с помощью CE MAPI для Windows Mobile
Скриншот тестового приложения: