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