c++ - 从 C++ 通过 activex 读取 Outlook 地址簿随机返回未知错误

标签 c++ vbscript outlook activex exchange-server

我目前正在编写一个程序,需要导入整个 Outlook 地址簿。该程序是用C++编写的(用gcc(MinGW)编译),我使用ActiveX连接到outlook。

这就是我所做的:

  1. 创建 Outlook.Application 的实例
  2. 获取当前 session
  3. 获取地址列表
  4. 对于AddressLists中的每个AddressList:获取AddressEntries
  5. 对于 AddressEntries 中的每个 AddressEntry:确定它是 ContactItem、ExchangeUser 还是其他内容
  6. 如果是 ContactItem:读取以下属性:FirstName、LastName、MailingAddressStreet、MailingAddressPostOfficeBox、MailingAddressPostalCode、MailingAddressCity、CompanyName、Department、JobTitle、PrimaryTelephoneNumber、OtherTelephoneNumber、BusinessFaxNumber 和 Email1Address
  7. 如果是 ExchangeUser:读取以下属性:FirstName、LastName、StreetAddress、PostalCode、City、CompanyName、Department、JobTitle、BusinessTelephoneNumber、MobileTelephoneNumber、Address
  8. 如果是另一种类型的 AddressEntry,请忽略它

只要地址簿中只有几个联系人,这就像一个魅力。 但现在我正在一家较大的公司中对其进行测试,该公司的地址簿中有大约 500 个联系人(ContactItems 和 ExchangeUsers),分为 22 个不同的地址列表。

在读取最大的AddressList(包含180个AddressEntries,所有ExchangeUsers)期间,Outlook的ActiveX组件突然返回未知错误(IDispatch->Invoke的返回值为0xFFFFFFFF)。它通常位于第 105 个和第 115 个 AddressEntry 之间,并且通常在读取 MobileTelephoneNumber 属性期间(但如果我将其注释掉,它只会在不同的调用中失败。

你知道我可能做错了什么吗? 我的印象是这与大量的连续调用有关。也许我应该在每次通话后进行某种清理?

无论如何, 任何帮助将不胜感激!

问候, ldx

编辑:代码 (为了简洁省略头文件)

1。应用程序类

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。域对象(示例)。其他域对象的实现类似

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 基类(_parent 是 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实用程序类

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是一个纯静态类

编辑 2:看起来这并不是一个与 C++ 相关的问题。 我想确保释放资源不是问题。 所以我在 VBS 中复制了我的代码(射击我):

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"

你猜怎么着:我也收到了错误!! 只有 VBS 似乎给了我一些额外的信息: 有时我会得到:

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

有时我会得到:

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

另外:发生错误后,outlook 完全崩溃:-|

救命!

最佳答案

您需要屈服于调度程序,以便您的宏不会终止。有多种方法可以解决它,但我发现结合 DoEventsSleep()似乎解决了这个问题。

这可能需要一些调整,具体取决于您的环境设置和性能需求。

屈服钩子(Hook)

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 依赖(放置在子模块顶部)

Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

关于c++ - 从 C++ 通过 activex 读取 Outlook 地址簿随机返回未知错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11052377/

相关文章:

vba - 使用 VBScript 调用 Outlook 过程

angularjs - Outlook如何在输入字段末尾添加 'x'?

.net - Outlook 约会功能区按钮事件

c++ - 禁止操作符 << 调用

c++ - C++中的二维对象数组

vbscript - 如何删除该字符串的右侧?

powershell - 无法在 Outlook 中使用 powershell 迭代电子邮件对象

c++ - 如何重置随机数引擎?

c++ - R 包开发中加载时设置的正确做法

vbscript - 在 vbscript 中将字符转换为 UTF 代码