2

I'm currently writing a program that needs to import the entire Outlook AddressBook. The program is written in C++ (compiled with gcc (MinGW)) and I use ActiveX to connect to outlook.

Here's what I do:

  1. Create an instance of Outlook.Application
  2. Get the current Session
  3. Get the AddressLists
  4. For each AddressList in AddressLists: get the AddressEntries
  5. For each AddressEntry in AddressEntries: Determine if it's a ContactItem, an ExchangeUser, or something else
  6. If it's a ContactItem: read the following properties: FirstName, LastName, MailingAddressStreet, MailingAddressPostOfficeBox, MailingAddressPostalCode, MailingAddressCity, CompanyName, Department, JobTitle, PrimaryTelephoneNumber, OtherTelephoneNumber, BusinessFaxNumber and Email1Address
  7. If it's an ExchangeUser: read the following properties: FirstName, LastName, StreetAddress, PostalCode, City, CompanyName, Department, JobTitle, BusinessTelephoneNumber, MobileTelephoneNumber, Address
  8. If it's yet another type of AddressEntry, ignore it

This works like a charm as long as there are just a few contacts in the addressbook. But now I'm test-driving it in a larger corporation where there are approximately 500 contacts in the addressbook (both ContactItems and ExchangeUsers), divided over 22 different AddressLists.

During the reading of the largest AddressList (containing 180 AddressEntries, all ExchangeUsers) Outlook's ActiveX component suddenly returns an unknown error (return-value of IDispatch->Invoke is 0xFFFFFFFF). It's usually somewhere between the 105th and the 115th AddressEntry, and usually during the reading of the MobileTelephoneNumber property (But if I comment that out, it just fails on a different call.

Any ideas what I could be doing wrong? I've got the impression that it's got something to do with the large amount of consecutive calls. Maybe there's some kind of cleanup that I ought to be doing after every call?

Anyway, Any help would be greatly appreciated!

Regards, ldx

Edit: The code (Omitting header files for brevity)

1. Application Class

Contact * MSOutlookContactSource::ToContact(SP<ContactUser> contactUser) const
{
  Contact * c = new Contact;
  c->setFirstName(contactUser->GetFirstName())
    .setLastName(contactUser->GetLastName())
    .setStreet(contactUser->GetMailingAddressStreet())
    .setStreetNr(contactUser->GetMailingAddressPostOfficeBox())
    .setZip(contactUser->GetMailingAddressPostalCode())
    .setCity(contactUser->GetMailingAddressCity())
    .setCompany(contactUser->GetCompanyName())
    .setDepartment(contactUser->GetDepartment())
    .setFunction(contactUser->GetJobTitle())
    .setTel1(contactUser->GetPrimaryTelephoneNumber())
    .setTel2(contactUser->GetOtherTelephoneNumber())
    .setFax(contactUser->GetBusinessFaxNumber())
    .setEmail(contactUser->GetEmail1Address());
  return c;
}
//----------------------------------------------------------------------
Contact * MSOutlookContactSource::ToContact(SP<ExchangeUser> exchangeUser) const
{
  Contact * c = new Contact;
  c->setFirstName(exchangeUser->GetFirstName())
    .setLastName(exchangeUser->GetLastName())
    .setStreet(exchangeUser->GetStreetAddress())
    .setZip(exchangeUser->GetPostalCode())
    .setCity(exchangeUser->GetCity())
    .setCompany(exchangeUser->GetCompanyName())
    .setDepartment(exchangeUser->GetDepartment())
    .setFunction(exchangeUser->GetJobTitle())
    .setTel1(exchangeUser->GetBusinessTelephoneNumber())
    .setTel2(exchangeUser->GetMobileTelephoneNumber())
    .setEmail(exchangeUser->GetAddress());
  return c;
}
//----------------------------------------------------------------------
vector<Contact *> MSOutlookContactSource::GetAllContacts() const
{
  LOG << "GetAllContacts" << endl;
  ActiveX::Initialize();
  vector<Contact *> retval;

  SP<Application> outlookApplication(Application::Create());
  SP<Namespace> session(outlookApplication->GetSession());
  SP<AddressLists> addressLists(session->GetAddressLists());
  long numAddressLists = addressLists->GetCount();
  LOG << "Found " << numAddressLists << " addressLists" << endl;
  for (long idxAddressLists = 1; idxAddressLists <= numAddressLists; ++idxAddressLists)
    {
      LOG << "Fetching addressList " << idxAddressLists << endl;
      SP<AddressList> addressList(addressLists->Item(idxAddressLists));
      SP<AddressEntries> addressEntries(addressList->GetAddressEntries());
      long numAddressEntries = addressEntries->GetCount();
      LOG << "Found " << numAddressEntries << " addressEntries" << endl;
      for (long idxAddressEntries = 1; idxAddressEntries <= numAddressEntries; ++idxAddressEntries)
        {
          LOG << "Fetching addressEntry " << idxAddressEntries << endl;
          SP<AddressEntry> addressEntry(addressEntries->Item(idxAddressEntries));
          SP<ContactUser> contactUser(addressEntry->GetContact());
          if (contactUser->IsNull())
            {
              SP<ExchangeUser> exchangeUser(addressEntry->GetExchangeUser());
              if (!exchangeUser->IsNull())
                {
                  LOG << "It's an ExchangeUser" << endl;
                  retval.push_back(ToContact(exchangeUser));
                }
              else
                LOG << "I don't know what it is => skipping" << endl;
            }
          else
            {
              LOG << "It's a ContactUser" << endl;
              retval.push_back(ToContact(contactUser));
            }
        }
    }

  ActiveX::Uninitialize();
  unsigned num_found = retval.size();
  LOG << "Found " << num_found << " contacts" << endl;
  return retval;
}
//----------------------------------------------------------------------

2. Domain object (example). The other domain objects are implemented similarly

ExchangeUser::ExchangeUser() : 
  ActiveXProxy(NULL)
{
}
//----------------------------------------------------------------------
ExchangeUser::ExchangeUser(IDispatch * parent) : 
  ActiveXProxy(parent)
{
}
//----------------------------------------------------------------------
ExchangeUser::~ExchangeUser()
{
}
//----------------------------------------------------------------------
ExchangeUser::ExchangeUser(const ExchangeUser & to_copy) : 
  ActiveXProxy(to_copy)
{
}
//----------------------------------------------------------------------
ExchangeUser & ExchangeUser::operator=(const ExchangeUser & to_copy)
{
  if (&to_copy != this)
    {
      *((ActiveXProxy *)this) = to_copy;
    }

  return *this;
}
//----------------------------------------------------------------------
bool ExchangeUser::IsNull()
{
  return _parent == NULL;
}
//----------------------------------------------------------------------
string ExchangeUser::GetFirstName()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"FirstName", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------
string ExchangeUser::GetLastName()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"LastName", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------
string ExchangeUser::GetStreetAddress()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"StreetAddress", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------
string ExchangeUser::GetPostalCode()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"PostalCode", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------
string ExchangeUser::GetCity()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"City", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------
string ExchangeUser::GetCompanyName()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"CompanyName", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------
string ExchangeUser::GetDepartment()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"Department", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------
string ExchangeUser::GetJobTitle()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"JobTitle", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------
string ExchangeUser::GetBusinessTelephoneNumber()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"BusinessTelephoneNumber", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------
string ExchangeUser::GetMobileTelephoneNumber()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"MobileTelephoneNumber", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------
string ExchangeUser::GetAddress()
{
  wstring wstr(ActiveX::GetProperty(_parent, L"Address", 0).bstrVal);
  string str(wstr.size(), ' ');
  copy(wstr.begin(), wstr.end(), str.begin());
  return str;
}
//----------------------------------------------------------------------

3. ActiveXProxy base class (_parent is an IDispatch *)

ActiveXProxy::ActiveXProxy() : 
  _parent(NULL)
{
}
//----------------------------------------------------------------------
ActiveXProxy::ActiveXProxy(IDispatch * parent) : 
  _parent(parent)
{
}
//----------------------------------------------------------------------
ActiveXProxy::~ActiveXProxy()
{
  if (_parent != NULL)
    {
      _parent->Release();
      _parent = NULL;
    }
}
//----------------------------------------------------------------------
ActiveXProxy::ActiveXProxy(const ActiveXProxy & to_copy) : 
  _parent(to_copy._parent)
{
}
//----------------------------------------------------------------------
ActiveXProxy & ActiveXProxy::operator=(const ActiveXProxy & to_copy)
{
  if (&to_copy != this)
    {
      _parent = to_copy._parent;
    }

  return *this;
}
//----------------------------------------------------------------------

4. ActiveX utility class

map<HRESULT, string> ActiveX::_errorTranslations;
unsigned ActiveX::_numInits = 0;
//----------------------------------------------------------------------
void ActiveX::Initialize()
{
  if (_numInits == 0)
    {
      CoInitialize(NULL);      

      _errorTranslations[DISP_E_BADPARAMCOUNT] = "DISP_E_BADPARAMCOUNT";
      _errorTranslations[DISP_E_BADVARTYPE] = "DISP_E_BADVARTYPE";
      _errorTranslations[DISP_E_EXCEPTION] = "DISP_E_EXCEPTION";
      _errorTranslations[DISP_E_MEMBERNOTFOUND] = "DISP_E_MEMBERNOTFOUND";
      _errorTranslations[DISP_E_NONAMEDARGS] = "DISP_E_NONAMEDARGS";
      _errorTranslations[DISP_E_OVERFLOW] = "DISP_E_OVERFLOW";
      _errorTranslations[DISP_E_PARAMNOTFOUND] = "DISP_E_PARAMNOTFOUND";
      _errorTranslations[DISP_E_TYPEMISMATCH] = "DISP_E_TYPEMISMATCH";
      _errorTranslations[DISP_E_UNKNOWNINTERFACE] = "DISP_E_UNKNOWNINTERFACE";
      _errorTranslations[DISP_E_UNKNOWNLCID ] = "DISP_E_UNKNOWNLCID ";
      _errorTranslations[DISP_E_PARAMNOTOPTIONAL] = "DISP_E_PARAMNOTOPTIONAL";
    }
  _numInits++;
}
//----------------------------------------------------------------------
void ActiveX::Uninitialize()
{
  if (_numInits > 0)
    {
      _numInits--;
      if (_numInits == 0)
        CoUninitialize();
    }
}
//----------------------------------------------------------------------
VARIANT ActiveX::GetProperty(IDispatch * from, LPOLESTR olePropertyName, int cArgs...)
{
  va_list marker;
  va_start(marker, cArgs);
  VARIANT *pArgs = new VARIANT[cArgs+1];

  char name[256];
  WideCharToMultiByte(CP_ACP, 0, olePropertyName, -1, name, 256, NULL, NULL);
  string propertyName(name);

  DISPID dispId;
  HRESULT hr = from->GetIDsOfNames(IID_NULL, &olePropertyName, 1, LOCALE_USER_DEFAULT, &dispId);
  if (SUCCEEDED(hr))
    {
      // Extract arguments...
      for(int i=0; i<cArgs; i++) 
        pArgs[i] = va_arg(marker, VARIANT);

      // Build DISPPARAMS
      DISPPARAMS dispParams = { NULL, NULL, 0, 0 };
      dispParams.cArgs = cArgs;
      dispParams.rgvarg = pArgs;

      EXCEPINFO excepInfo;
      VARIANT vProperty;
      hr = from->Invoke(dispId, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispParams, &vProperty, &excepInfo, NULL);
      if (SUCCEEDED(hr))
        {
          va_end(marker);
          delete [] pArgs;
          return vProperty;
        }
      else
        {
          va_end(marker);
          delete [] pArgs;

          stringstream errorMessage;
          errorMessage << "Failed to Invoke property-get on " << propertyName << " (" << hr << " - " << TranslateError(hr);
          if (hr == DISP_E_EXCEPTION)
            errorMessage << " exception: " << excepInfo.wCode << " - " << excepInfo.bstrDescription;
          errorMessage << " )";
          throw ActiveXException(errorMessage.str());
        }
    }
  else
    {
      va_end(marker);
      delete [] pArgs;
      throw ActiveXException(string("Failed to get DISPID of property ") + propertyName);
    }
}
//----------------------------------------------------------------------
VARIANT ActiveX::CallMethod(IDispatch * on, LPOLESTR oleMethodName, int cArgs...)
{
  va_list marker;
  va_start(marker, cArgs);
  VARIANT *pArgs = new VARIANT[cArgs+1];

  char name[256];
  WideCharToMultiByte(CP_ACP, 0, oleMethodName, -1, name, 256, NULL, NULL);
  string methodName(name);

  DISPID dispId;
  HRESULT hr = on->GetIDsOfNames(IID_NULL, &oleMethodName, 1, LOCALE_USER_DEFAULT, &dispId);
  if (SUCCEEDED(hr))
    {
      // Extract arguments...
      for(int i=0; i<cArgs; i++) 
        pArgs[i] = va_arg(marker, VARIANT);

      // Build DISPPARAMS
      DISPPARAMS dp = { NULL, NULL, 0, 0 };
      dp.cArgs = cArgs;
      dp.rgvarg = pArgs;

      // Make the call!
      EXCEPINFO excepInfo;
      VARIANT result;
      hr = on->Invoke(dispId, IID_NULL, LOCALE_SYSTEM_DEFAULT, 
                      DISPATCH_METHOD, &dp, &result, &excepInfo, NULL);
      if(SUCCEEDED(hr))
        return result;
      else
        {
          va_end(marker);
          delete [] pArgs;

          stringstream errorMessage;
          errorMessage << "Failed to call method " << methodName << " (" << hr << " - " << TranslateError(hr);
          if (hr == DISP_E_EXCEPTION)
            errorMessage << " exception: " << excepInfo.wCode << " - " << excepInfo.bstrDescription;
          errorMessage << " )";
          throw ActiveXException(errorMessage.str());
        }
    }
  else
    {
      va_end(marker);
      delete [] pArgs;
      throw ActiveXException(string("Failed to get DISPID of method ") + methodName);
    }
}
//----------------------------------------------------------------------
string ActiveX::TranslateError(HRESULT error)
{
  return _errorTranslations[error];
}
//----------------------------------------------------------------------

ActiveX is a pure static class

Edit 2: It looks like this is not so much a C++ related problem. I wanted to make sure that freeing resources was not the issue. So I duplicated my code in VBS (shoot me):

Set objFSO = CreateObject("Scripting.FileSystemObject")
If Not objFSO.FileExists("out.log") Then
   objFSO.CreateTextFile("out.log")
End If
Set objLog = objFSO.OpenTextFile("out.log", 2, True)

Set objOutlook = CreateObject("Outlook.Application")
Set objSession = objOutlook.Session
Set objAddressLists = objSession.AddressLists
objLog.WriteLine("--- Found " + CStr(objAddressLists.Count) + " AddressLists")

For i = 1 To objAddressLists.Count
    objLog.WriteLine("--- AddressList " + CStr(i))
    Set objAddressList = objAddressLists.Item(i)
    objLog.WriteLine("+++ AddressListName = " + objAddressList.Name)
    Set objAddressEntries = objAddressList.AddressEntries
    objLog.WriteLine("--- AddressList has " + CStr(objAddressEntries.Count) + " AddressEntries")

    For j = 1 To objAddressEntries.Count

      objLog.WriteLine("--- AddressEntry " + CStr(i) + "." + CStr(j))
      Set objAddressEntry = objAddressEntries.Item(j)    

      If objAddressEntry.AddressEntryUserType = olExchangeUserAddressEntry Then

        Set objExchangeUser = objAddressEntry.GetExchangeUser()
        objLog.WriteLine("Exchangeuser: " + _
                         objExchangeUser.FirstName + "|" + _
                         objExchangeUser.LastName + "|" + _
                         objExchangeUser.StreetAddress + "|" + _
                         objExchangeUser.PostalCode + "|" + _
                         objExchangeUser.City + "|" + _
                         objExchangeUser.CompanyName + "|" + _
                         objExchangeUser.Department + "|" + _
                         objExchangeUser.JobTitle + "|" + _
                         objExchangeUser.BusinessTelephoneNumber + "|" + _
                         objExchangeUser.MobileTelephoneNumber + "|" + _
                         objExchangeUser.Address)
        Set objExchangeUser = Nothing

      ElseIf objAddressEntry.AddressEntryUserType = olOutlookContactAddressEntry Then

        Set objContact = objAddressEntry.GetContact()
        objLog.WriteLine("Contactuser: " + _
                         objContact.FirstName + "|" + _
                         objContact.LastName + "|" + _
                         objContact.MailingAddressStreet + "|" + _
                         objContact.MailingAddressPostOfficeBox + "|" + _
                         objContact.MailingAddressPostalCode + "|" + _
                         objContact.MailingAddressCity + "|" + _
                         objContact.CompanyName + "|" + _
                         objContact.Department + "|" + _
                         objContact.JobTitle + "|" + _
                         objContact.PrimaryTelephoneNumber + "|" + _
                         objContact.OtherTelephoneNumber + "|" + _
                         objContact.BusinessFaxNumber + "|" + _
                         objContact.Email1Address)
        Set objContact = Nothing
      End If
      Set objAddressEntry = Nothing
    Next
    Set objAddressEntries = Nothing
    Set objAddressList = Nothing
Next

objTextFile.Close
MsgBox "Done"

And guess what: I get the error too!! Only VBS seems to give me some extra info: Sometimes I get:

Error: The server threw an exception
Code: 80010105
Source: (null)

And sometimes I get:

Error: The remote procedure call failed
Code: 800706BE
Source: (null)

Also: after the error, outlook crashes entirely :-|

HELP!

ldx
  • 2,536
  • 2
  • 18
  • 27
  • You should share your code for context. You likely are [not properly disposing of the COM resources](http://www.add-in-express.com/creating-addins-blog/2008/10/30/releasing-office-objects-net/). – SliverNinja - MSFT Jun 15 '12 at 14:28
  • hmmm, something seems to F up my formatting... – ldx Jun 15 '12 at 16:02

2 Answers2

1

You need to yield to the scheduler so that your Macro isn't terminated. There are several ways to approach it, but I found combining DoEvents with Sleep() seemed to get around the issue.

This may take some adjustment depending on your environment settings and performance needs.

Yielding Hooks

For j = 1 To objAddressEntries.Count
    ' allow other events to trigger
    For intWait = 1 To 15000
        DoEvents
    Next  
    ' Yield to other events      
    If (j Mod 50 = 0) Then
        Sleep (5000)
    End If

    ' process address entry
    '...

Next

Sleep Dependency (place at top of module subs)

Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Community
  • 1
  • 1
SliverNinja - MSFT
  • 31,051
  • 11
  • 110
  • 173
  • I'm afraid that won't solve it. First of all: the vbs example is not a macro in outlook, its a simple vbs file (executed by windows scripting host) so there are no other events. Secondly, that wouldn't solve the problem in my C++ application and thirdly I already tried putting in a sleep in the second for-loop (so between processing of each AddressEntry) end that didn't help... – ldx Jun 18 '12 at 19:15
1

The errors are RPC_S_CALL_FAILED and RPC_E_SERVERFAULT. This is an indication that you open too many objects and run out of the RPC channels (the limit is imposed by the Exchange server). Release all Outlook objects as soon as you are down with them and avoid using multiple dot notation (which creates implicit variables that you cannot explicitly reference and release).

Dmitry Streblechenko
  • 62,942
  • 4
  • 53
  • 78
  • Yes, I've read that too somewhere. I've updated the vbs code to explicitly set the used objects to Nothing again (I've read that that is the way to dispose of an object in vbs (I have no prior experience with vbs)) But the result stays the same. Also, as you can see, I explicitly call IDispatch->Release in the destructor of the base object in C++ (ActiveXProxy) and I know it works (logging). So I guess these objects aren't properly released then? Any idea on how I should release them? – ldx Jun 18 '12 at 20:07
  • What is your updated script? The script above never sets objExchangeUser to Nothing. – Dmitry Streblechenko Jun 18 '12 at 20:24
  • oops, typo. Sorry about that. I've now corrected it. Also some other changes in the script. The result is still the same though... – ldx Jun 19 '12 at 06:58