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:
- Create an instance of Outlook.Application
- Get the current Session
- Get the AddressLists
- For each AddressList in AddressLists: get the AddressEntries
- For each AddressEntry in AddressEntries: Determine if it's a ContactItem, an ExchangeUser, or something else
- If it's a ContactItem: read the following properties: FirstName, LastName, MailingAddressStreet, MailingAddressPostOfficeBox, MailingAddressPostalCode, MailingAddressCity, CompanyName, Department, JobTitle, PrimaryTelephoneNumber, OtherTelephoneNumber, BusinessFaxNumber and Email1Address
- If it's an ExchangeUser: read the following properties: FirstName, LastName, StreetAddress, PostalCode, City, CompanyName, Department, JobTitle, BusinessTelephoneNumber, MobileTelephoneNumber, Address
- 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!