воскресенье, 14 ноября 2010 г.

Доступ к Exchange почте по протоколу Mapi

Хочу рассказать сегодня как получить список писем, аттачей из Windows Exchange и программно отсылать письма через протокол Simple MAPI.
Для этого нам понадобится небольшой класс. Он слегка недоделан: отправка почты и получение списка аттачей, но это доделать не так сложно.
Описание протокола Simple MAPI можно почитать по адресу: http://msdn.microsoft.com/en-us/library/dd296728(v=VS.85).aspx

Итак, описание класса на C++ (Builder):
#ifndef SimpleMapiH
#define SimpleMapiH

using namespace std;

#include <windows.h>
#include <windowsx.h>
#include <mapi.h>
#include <winbase.h>
#include <vector>

//структура с информацией о письме
struct TMail {
  AnsiString from;
  AnsiString subj;
  unsigned int files;
  TDateTime date;
  //указатели на письмо
  LPSTR lpszMessageID;
  MapiMessage *lpMessage;
};

class SimpleMapi {
   private:
      //библиотека и указатели на нужные нам функции
      HANDLE hMAPILib;
      LPMAPILOGON lpfnMAPILogon;
      LPMAPIFINDNEXT lpfnMAPIFindNext;
      LPMAPILOGOFF lpfnMAPILogoff;
      LPMAPIREADMAIL lpfnMAPIReadMail;
      LPMAPIFREEBUFFER lpfnMAPIFreeBuffer;

      //текущая сессия
      LHANDLE lhSession;

      //буфферы
      char szSeedMessageID[512];
      char szMessageID[512];
      LPSTR lpszSeedMessageID;
      LPSTR lpszMessageID;

      //указатель на письмо
      MapiMessage *lpMessage;

      //тип ошибки
      ULONG err;

      //разбор даты
      TDateTime __fastcall ParseDate(AnsiString date);
      
   public:
        //полный список писем
        vector<tmail> MailList;

        __fastcall SimpleMapi();
        //подключение к Exchenge
        bool __fastcall Connect();
        //перейти к первому письму из списка
        bool __fastcall GetFirst();
        //перейти к следующему письму из списка
        bool __fastcall GetNext();
        //получить указатель на полную информацию о текущем письме
        bool __fastcall GetElement();
        //заполнить структуру текущим письмом
        TMail __fastcall Fetch();
        //получить список всех писем (заполняем вектор)
        bool __fastcall GetList();
        //отключаемся от Exchange
        void __fastcall Disconnect();
        //отправить письмо - не реализовано
        void __fastcall SendMail();
        //получить список приложений письма по указателю
        vector<ansistring> __fastcall GetAttachs(LPSTR lmID, MapiMessage *lpMes);
};

#endif

#include <vcl.h>
#pragma hdrstop
#include "SimpleMapi.h"
#pragma package(smart_init)

__fastcall SimpleMapi::SimpleMapi() {
  lpszSeedMessageID=&szSeedMessageID[0];
  lpszMessageID=&szMessageID[0];
  *lpszSeedMessageID = '\0';

  //необходимые нам функции из мапи
  hMAPILib = LoadLibrary("MAPI32.DLL");
  lpfnMAPILogon      = (LPMAPILOGON)      GetProcAddress(hMAPILib, "MAPILogon");
  lpfnMAPILogoff     = (LPMAPILOGOFF)     GetProcAddress(hMAPILib, "MAPILogoff");
  lpfnMAPIFindNext   = (LPMAPIFINDNEXT)   GetProcAddress(hMAPILib, "MAPIFindNext");
  lpfnMAPIReadMail   = (LPMAPIREADMAIL)   GetProcAddress(hMAPILib, "MAPIReadMail");
  lpfnMAPIFreeBuffer = (LPMAPIFREEBUFFER) GetProcAddress(hMAPILib, "MAPIFreeBuffer");
}

//подключаемся к мапи
bool __fastcall SimpleMapi::Connect() {
  err = (*lpfnMAPILogon)(0L, "Microsoft Outlook", NULL, 0L, 0L, &lhSession);
  if(err != SUCCESS_SUCCESS) {
    FreeLibrary(hMAPILib);
    return false;
  }
  return true;
}

//получаем первое письмо
bool __fastcall SimpleMapi::GetFirst() {
  //MAPI_GUARANTEE_FIFO - сортировка по дате
  err = (*lpfnMAPIFindNext)(lhSession, 0L, NULL, lpszSeedMessageID, MAPI_GUARANTEE_FIFO, 0L, lpszMessageID);
  return err == SUCCESS_SUCCESS;
}

//получаем следующее письмо
bool __fastcall SimpleMapi::GetNext() {
  //MAPI_GUARANTEE_FIFO - сортировка по дате
  //чистим буфер
  (*lpfnMAPIFreeBuffer)(lpMessage);
  lstrcpy(lpszSeedMessageID, lpszMessageID);
  //переходим
  err = (*lpfnMAPIFindNext)(lhSession, 0L, NULL, lpszSeedMessageID, MAPI_GUARANTEE_FIFO, 0L, lpszMessageID);
  return err == SUCCESS_SUCCESS;
}

//заполняет указатель на письмо
bool __fastcall SimpleMapi::GetElement() {
  //MAPI_SUPPRESS_ATTACH - аттачи тоже загружаем
  err = (*lpfnMAPIReadMail)(lhSession, 0L, lpszMessageID, MAPI_SUPPRESS_ATTACH, 0L, &lpMessage);
  return err == SUCCESS_SUCCESS;
}

//заполним структуру данными письма
TMail __fastcall SimpleMapi::Fetch() {
  TMail buf;
  if((lpMessage->lpOriginator->lpszName != NULL) && lpMessage->lpOriginator->lpszName[0] != '\0') {
    buf.from = lpMessage->lpOriginator->lpszName;
  } else {
    buf.from = lpMessage->lpOriginator->lpszAddress;
  }
  buf.subj = AnsiString(lpMessage->lpszSubject);
  buf.files = (unsigned int)lpMessage->nFileCount;
  buf.date = SimpleMapi::ParseDate(AnsiString(lpMessage->lpszDateReceived));
  buf.lpszMessageID = lpszMessageID;
  buf.lpMessage = lpMessage;
  return buf;
}

//распарсить дату письма (учтем перевод стрелок)
TDateTime __fastcall SimpleMapi::ParseDate(AnsiString date) {
  //пример: 2010/04/02 12:02

  unsigned short y,m,d,h,i,s;
  y = (unsigned short)date.SubString(1, 4).ToInt();
  m = (unsigned short)date.SubString(6, 2).ToInt();
  d = (unsigned short)date.SubString(9, 2).ToInt();
  h = (unsigned short)date.SubString(12, 2).ToInt();
  i = (unsigned short)date.SubString(15, 2).ToInt();
  
  TTimeZoneInformation tzi;
  if(GetTimeZoneInformation(&tzi) == TIME_ZONE_ID_STANDARD) {
    //в зимнее время добавляем 1 час
    h++;
  }
  TDateTime dt(y, m, d);
  TDateTime tm(h, i, 0, 0);
  return dt + tm;
}

//заполним вектор письмами
bool __fastcall SimpleMapi::GetList() {
  if(!Connect()) return false;
  if(!GetFirst()) return false;
  
  TMail buf;
  int i = 0;
  while(err == SUCCESS_SUCCESS) {
    if(GetElement()) {
      buf = Fetch();
      MailList.push_back(buf);
    }
    GetNext();
    
    if(i%1000 == 0 && i > 0) {
      //переделать на вопрос
      if( Application->MessageBox("Загрузка списка писем может занять продолжительное время. Продолжить?",
                              Application->Title.c_str(),
                              MB_ICONQUESTION | MB_YESNO) == IDNO ) break;
    }

    i++;
  }
  Disconnect();

  return true;
}

//отключаемся
void __fastcall SimpleMapi::Disconnect() {
  //закрываем подключение
  (*lpfnMAPILogoff)(lhSession, 0L, 0L, 0L);
  //освобождаем библиотеку
  FreeLibrary(hMAPILib);
}

void __fastcall SimpleMapi::SendMail() {
  //TODO
  /*
  документация: http://msdn.microsoft.com/en-us/library/dd296721(v=VS.85).aspx
  примеры:
       - http://stackoverflow.com/questions/2350011/how-do-i-programmatically-send-email-w-attachment-to-a-known-recipient-using-mapi
       - http://www.codeproject.com/KB/IP/SendTo.aspx
       - http://www.codeguru.com/forum/archive/index.php/t-105158.html (без приложений)
  */
}

//получить список аттачей
//аттачи сохраняются во временную дирректорию
vector<AnsiString> __fastcall SimpleMapi::GetAttachs(LPSTR lmID, MapiMessage *lpMes) {
  vector<AnsiString> atachs;
  err = (*lpfnMAPIReadMail)(lhSession, 0L, lmID, MAPI_SUPPRESS_ATTACH, 0L, &lpMes);
  if(err == SUCCESS_SUCCESS) {
    //TODO
    //for(int i = 0; i < lpMes->nFileCount; i++) {
    //lpMes->lpFiles->lpszPathName;
    //lpMes->lpFiles->lpszFileName;
    //lpMes->lpFiles++;
    //}
  }
  return atachs;
}