Появилась необходимость программно отправлять 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
Скриншот тестового приложения:

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

Leave a Reply

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

К.

Как сменить тему в Windows Mobile программно

Как сменить тему в Windows Mobile программноЗадался тут вопросом о программной смене темы в Windows Mobile. Оказывается это не так просто, как может показаться. Информация с MSDN’овских форумов и из самой MSDN мягко говоря не соответствует действительности. В Интернете полно топиков с подобными вопросами, но вменяемый результат выудить оттуда тоже почти нереально.

После нескольких часов мучений получил вроде вполне рабочий результат. А результатом, собственно, стал небольшой класс для установки и получения темы для Windows Mobile.

Но перед тем, как мы перейдем к рассмотрению примера, давайте, все же, определимся с последовательностью действий:

  • Имя файла текущей темы находится в реестре по адресу “HKEY_CURRENT_USER\Software\Microsoft\Today\Skin”
  • Первым шагом для смены темы в Windows Mobile является запуск утилиты \Windows\wcrload.exe c параметрами /noui /nouninstall /delete 0 “путь_к_файлу_темы.tsk” (кавычки нужны для того, чтобы правильно обрабатывались пути с пробелами. Это важно!).
  • Затем надо удалить в реестре значение по адресу “HKEY_LOCAL_MACHINE\Software\Microsoft\Color\BaseHue” иначе после применения темы изменится только фоновая картинка в Today, а цветовая схема останется прежней (тоже долго искал почему не применяется тема полностью, это тоже важный момент).
  • Затем надо удалить значение в реестре по адресу “HKEY_CURRENT_USER\Software\Microsoft\Today\UseStartImage”.
  • После этого нужно прописать прописать путь к файлу темы в реестре по адресу “HKEY_CURRENT_USER\Software\Microsoft\Today\Skin”.
  • И последнее, что нужно сделать, это разослать всем окнам сообщение об изменении настроек системы с помощью ::PostMessage(HWND_BROADCAST, WM_WININICHANGE, 0xF2, 0)

А теперь пример:

#ifndef _MOBILE_THEME_SWITCH_H
#define _MOBILE_THEME_SWITCH_H

#include <wx/wx.h>
#include <wx/filename.h>
#include <wx/msw/registry.h>

class MobileThemeSwitch
{
public:
	static wxString GetTheme()
	{
		wxString result;
		do 
		{
			wxRegKey key(wxRegKey::HKCU, wxT("Software\\Microsoft\\Today"));
			if(!key.Exists()) break;
			if(!key.Open(wxRegKey::Read)) break;
			const wxString skinValueName(wxT("Skin"));
			if(!key.HasValue(skinValueName)) break;
			if(!key.QueryValue(skinValueName, result)) break;
		} 
		while (false);
		if(result.IsEmpty()) result = _("default");
		return result;
	}
	static bool SetTheme(const wxString & value)
	{
		wxRegKey  * key(NULL);
		do 
		{
			wxString wceLoadFileName = wxT("\\Windows\\wceload.exe");
			if(!wxFileExists(wceLoadFileName)) 
			{
				wxLogDebug(_("'wceload.exe' does not exist"));
				break;
			}
			wxString commandLine = wxString::Format(
				wxT("%s /noui /nouninstall /delete 0 \"%s\""),
				wceLoadFileName.GetData(),
				value.GetData());
			long execResult = wxExecute(commandLine, wxEXEC_SYNC);
			if(execResult != 0)
			{
				wxLogDebug(_("'wceload.exe' returned error (%l)"), execResult);
				break;
			}
			key = new wxRegKey(wxRegKey::HKLM, wxT("Software\\Microsoft\\Color"));
			if(!key->Exists())
			{
				wxLogDebug(_("'Software\\Microsoft\\Color' registry key does not exist"));
				break;
			}
			if(!key->Open(wxRegKey::Write))
			{
				wxLogDebug(_("Unable to open registry key 'Software\\Microsoft\\Color'"));
				break;
			}
			const wxString baseHueValueName(wxT("BaseHue"));
			if(key->HasValue(baseHueValueName))
			{
				key->DeleteValue(baseHueValueName);
			}
			key->Close();
			wxDELETE(key);
			key = new wxRegKey(wxRegKey::HKCU, wxT("Software\\Microsoft\\Today"));
			if(!key->Exists())
			{
				wxLogDebug(_("'Software\\Microsoft\\Today' registry key does not exist"));
				break;
			}
			if(!key->Open(wxRegKey::Write))
			{
				wxLogDebug(_("Unable to open registry key 'Software\\Microsoft\\Today'"));
				break;
			}
			const wxString useStartImageValueName(wxT("UseStartImage"));
			if(key->HasValue(useStartImageValueName))
			{
				key->DeleteValue(useStartImageValueName);
			}
			const wxString skinValueName(wxT("Skin"));
			if(!key->SetValue(skinValueName, value))
			{
				wxLogDebug(_("Unable to change value 'Skin' in 'Software\\Microsoft\\Today'"));
				break;
			}
			key->Close();
			wxDELETE(key);
			::PostMessage(HWND_BROADCAST, WM_WININICHANGE, 0xF2, 0);
			return true;
		} 
		while (false);
		wxDELETE(key);
		return false;
	}
};

#endif

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

W.

Windows® Marketplace for Mobile Developer Strategy

Сегодня Microsoft опубликовала информацию о том, как будет функционировать online-сервис продажи мобильных приложений Windows® Marketplace for Mobile.

Итак, информация к размышлению:

  1. Сколько будет зарабатівать разработчик на продаже своих приложений?
    • Разработчик будет получать 70% от продаж в Windows Marketplace for Mobile (на сколько я понимаю, процент сравним с AppStore от Apple).
    • Приложение может продаваться на 29 торговых площадках (markets) с ценовым разграничением по каждой из них.
    • Также приложение может распространяться бесплатно, т.е. в Windows Marketplace for Mobile можно будет запостить и бесплатные приложения.
  2. Что нужно для регистрации?
    • Информация о регистрации будет доступна чуть позже (весной). Прием приложений планируется начать к лету.
    • Разработчики смогут выкладывать 5 приложений ежегодно за $99. И еще прийдется платить по $99 за каждое дополнительное приложение.
    • Для студентов, участвующих в программе DreamSpark, цены будут значительно снижены.
  3. Что нужно для того, чтобы приложение попалов Marketplace?
    • Сказано, что значительное внимание будет уделено совместимости и корректной работе приложений на мобильных устройствах. Планируется организовать процесс сертификации и тестирования приложений, выкладываемых в Marketplace.
    • разработчикам будет предоставляться детальная информация о результатах сертификации на Windows Marketplace for Mobile developer portal.
  4. Что нужно для того, чтобы начать разработку для Windows Mobile?
    • Можно использовать Visual Studio и .NET Compact Framework 3.5 (я так понимаю, они это говорят в рекламных целях, C++ еще вроде никто не отменял).
    • Скачать Windows Mobile 6.0 SDK и ознакомиться с информацией на http://developer.windowsmobile.com.

Ознакомиться с пресс-релизом можно здесь.

Интервью с Inigo Lopez, Marketplace Product Manager: