Сообщений 10    Оценка 265        Оценить  
Система Orphus

Получение информации о COM-интерфейсах

На случай, если вы не знаете…

Автор: Павел Коломиец
Источник: RSDN Magazine #3-2003
Опубликовано: 01.01.2004
Исправлено: 10.12.2016
Версия текста: 1.0.1
Введение
Смотрим MSDN
Интерфейс ITypeLib
Интерфейс ITypeInfo
Пробуем
Итого
Литература

Демонстрационный проект

Введение

На RSDN есть статья Павла Блудова “Known IUnknown”, в которой он рассуждает о получении информации об интерфейсах COM-объекта и методах COM-интерфейсов. Предложенные им подходы (перебор всех возможных GUID, сканирование реестра, разбор pdb-файлов и т.д.) являются, на мой взгляд,… э… гм… несколько экзотичными. Существуют более удобные стандартные методы.

Для получения информации о COM-объектах существуют специальные утилиты (смотри хотя бы входящий в состав Visual Studio «OLE/COM Object Viewer»), которые и расскажут, и покажут, и IDL напишут. Но иногда необходимо получать эти данные «на лету», во время выполнения программы. Механизмам получения информации об интерфейсах и посвящена эта статья.

Смотрим MSDN

Информация о COM-объектах содержится в библиотеке типов (type library) – двоичном эквиваленте описания объектов на языке IDL, при условии, что таковая имеется. Обычно это файл с расширением .tlb. Кроме того, библиотека типов очень часто содержится в ресурсах исполняемого файла или dll, содержащих код COM-сервера (.exe, .dll и .olb).

Для доступа к этим данным используются два интерфейса: ITypeLib и ITypeInfo.

Интерфейс ITypeLib

Для получения информации из библиотеки типов используется интерфейс ITypeLib. Он позволяет получить общую информацию о библиотеке типов, а также предоставляет интерфейс ITypeInfo для описания каждого из перечисленных объектов (см. рисунок 1).


Рисунок 1.

Получить интерфейс ITypeLib можно с помощью функций LoadTypeLib и RegisterTypeLib. Кроме того, если кокласс поддерживает интерфейс IProvideClassInfo, то с его помощью также можно получить ITypeLib. Расширенная версия интерфейса IProvideClassInfoIProvideClassInfo2 позволяет сразу получить GUID интерфейса, используемого в coclass-е по умолчанию.

Вот некоторые функции интерфейса ITypeLib:

UINT GetTypeInfoCount();

Этот метод возвращает количество объектов в библиотеке типов.

HRESULT GetTypeInfo(
  unsignedint index, 
  ITypeInfo ** ppTInfo);

Метод GetTypeInfo позволяет получить описание элемента с номером index.

HRESULT GetDocumentation(
  int index, 
  BSTR FAR* pBstrName, 
  BSTR FAR* pBstrDocString, 
  unsignedlong FAR* pdwHelpContext,
  BSTR FAR* pBstrHelpFile);

Этот метод позволяет получить для элемента с номером index название (pBstrName), описание (pBstrDocString), файл справки (pBstrHelpFile), индекс в файле справки (pdwHelpContext).

Полученные строки необходимо удалить самостоятельно (вызвать SysFreeString). Если один из элементов не нужен, вместо указателя передается NULL

Существует также расширенная версия этого интерфейса – ITypeLib2. Он позволяет получать информацию об элементах в различных контекстах локализации (функция GetDocumentation2), а также предоставляет некоторые другие возможности.

Интерфейс ITypeInfo

Для получения информации об объектах, описанных в библиотеке типов (интерфейсах, coclass-ах и т.д.), используется интерфейс ITypeInfo. С его помощью можно получить свойства элементов (название, GUID и т.д.), а также информацию о методах интерфейсов.

Вот некоторые функции этого интерфейса.

HRESULT GetTypeAttr(TYPEATTR FAR* FAR* ppTypeAttr);

Эта функция позволяет получить структуру TYPEATTR, содержащую GUID элемента (если таковой имеется) – guid; тип элемента – typekind; количество методов в интерфейсе – cFuncs. Структуру не надо создавать заранее, достаточно просто передать NULL. Освободить память, выделенную для этой структуры, можно при помощи функции ReleaseTypeAttr.

HRESULT GetFuncDesc(unsignedint index, FUNCDESC FAR* FAR* ppFuncDesc);

Эта функция позволяет получить структуру FUNCDESC, описывающую метод с заданным индексом. Эта структура содержит тип функции – funckind; количество параметров – cParams; тип результата, возвращаемого функцией – elemdescFunc; указатель на массив, содержащий типы параметров метода – lprgelemdescParam; идентификатор метода – memid. Структуру не надо создавать заранее, достаточно просто передать NULL.

ПРИМЕЧАНИЕ

Информация о типах параметров и типе возврата фактически хранится в виде поля типа TYPEDESC. Ее поле vt является описанием automation-типа, т.е. может принимать значения: VT_BSTR, VT_I2, VT_DATE и т.д. Пример приведен ниже.

ReleaseFuncDesc уничтожает созданную с помощью GetFuncDesc структуру.

HRESULT GetDocumentation(
  MEMBERID memid, 
  BSTR FAR* pBstrName, 
  BSTR FAR* pBstrDocString, 
  unsignedlong FAR* pdwHelpContext, 
  BSTR FAR* pBstrHelpFile);

Позволяет получить для метода с номером memid (см. описание функции GetFuncDesc) название (pBstrName), описание (pBstrDocString), файл справки (pBstrHelpFile), индекс в файле справки (pdwHelpContext).

ПРИМЕЧАНИЕ

Полученные строки необходимо удалить самостоятельно (вызвать SysFreeString). Если один из элементов не нужен, вместо указателя предается NULL.

Листинг 1. Определение типа (TYPEDESC).
CString DecodeTYPEDESC(ITypeInfo* pti, TYPEDESC* ptdesc)
{
  CString str;
  
  // Это указатель?if (ptdesc->vt == VT_PTR)
  {
    // Тогда проверяем тип указателя
    str = DecodeTYPEDESC(pti, ptdesc->lptdesc);
    // И добавляем "*"
    str += _T("*");
    return str.AllocSysString();
  }
  
  // Это массив?if ((ptdesc->vt & 0x0FFF) == VT_CARRAY)
  {
    // Тогда определяем тип его элементов...
    str = DecodeTYPEDESC(pti, &ptdesc->lpadesc->tdescElem);

    // ...и размерность
    CString strTemp;
    for (USHORT n = 0; n < ptdesc->lpadesc->cDims; n++)
    {
      strTemp.Format(_T("[%d]"), ptdesc->lpadesc->
        rgbounds[n].cElements);
      str += strTemp;
    }

    return str;
  }

  // Это SAFEARRAY?if ((ptdesc->vt & 0x0FFF) == VT_SAFEARRAY)
  {
    str = _T("SAFEARRAY(") + DecodeTYPEDESC(pti, ptdesc->lptdesc);
    return str;
  }
  
  // Это пользовательский тип (UDT)?...if (ptdesc->vt == VT_USERDEFINED)
  {
    ASSERT(pti);
    ITypeInfo* ptiRefType = NULL;
    // ...тогда получаем его название
    HRESULT hr = pti->GetRefTypeInfo(ptdesc->hreftype, &ptiRefType);
    if (SUCCEEDED(hr))
    {
      BSTR            bstrName = NULL;
      BSTR            bstrDoc = NULL;
      BSTR            bstrHelp = NULL;
      DWORD           dwHelpID;
      hr = ptiRefType->GetDocumentation(MEMBERID_NIL, &bstrName,
        &bstrDoc, &dwHelpID, &bstrHelp);
      if (FAILED(hr))
        return _T("ITypeInfo::GetDocumentation() failed");
      
      str = bstrName;
      
      SysFreeString(bstrName);
      SysFreeString(bstrDoc);
      SysFreeString(bstrHelp);
      
      ptiRefType->Release();
    }
    elsereturn _T("GetRefTypeInfo failed");
    
    return str;
  }

  VARTYPE vt = ptdesc->vt & ~0xF000;
  if (vt <= VT_CLSID)
    // Если это не специальный случай, просто получаем описание типа
    DecodeVARTYPE(vt, str);
  else
    str = _T("BAD VARTYPE");

  return str;
}
Листинг 2. Определение типа (VARTYPE).
        void DecodeVARTYPE(VARTYPE vt, CString &strDesc)
{
  switch (vt)
  {
  case VT_EMPTY: strDesc = _T("void"); break;
  case VT_NULL: strDesc = _T("NULL"); break;
  case VT_I2: strDesc = _T("short"); break;
  case VT_I4: strDesc = _T("long"); break;
  case VT_R4: strDesc = _T("single"); break;
  case VT_R8: strDesc = _T("double"); break;
  case VT_CY: strDesc = _T("CURRENCY"); break;
  case VT_DATE: strDesc = _T("DATE"); break;
  case VT_BSTR: strDesc = _T("BSTR"); break;
  case VT_DISPATCH: strDesc = _T("IDispatch*"); break;
  case VT_ERROR: strDesc = _T("SCODE"); break;
  case VT_BOOL: strDesc = _T("BOOL"); break;
  case VT_VARIANT: strDesc = _T("VARIANT"); break;
  case VT_UNKNOWN: strDesc = _T("IUnknown*"); break;
  case VT_I1: strDesc = _T("char"); break;
  case VT_UI1: strDesc = _T("unsigned char"); break;
  case VT_UI2: strDesc = _T("unsigned short"); break;
  case VT_UI4: strDesc = _T("unsigned long"); break;
  case VT_I8: strDesc = _T("int64"); break;
  case VT_UI8: strDesc = _T("uint64"); break;
  case VT_INT: strDesc = _T("int"); break;
  case VT_UINT: strDesc = _T("unsigned int"); break;
  case VT_VOID: strDesc = _T("void"); break;
  case VT_HRESULT: strDesc = _T("HRESULT"); break;
  case VT_PTR: strDesc = _T("void*"); break;
  case VT_SAFEARRAY: strDesc = _T("SAFEARRAY"); break;
  case VT_CARRAY: strDesc = _T("CARRAY"); break;
  case VT_USERDEFINED: strDesc = _T("USERDEFINED"); break;
  case VT_LPSTR: strDesc = _T("LPSTR"); break;
  case VT_LPWSTR: strDesc = _T("LPWSTR"); break;
  case VT_FILETIME: strDesc = _T("FILETIME"); break;
  case VT_BLOB: strDesc = _T("BLOB"); break;
  case VT_STREAM: strDesc = _T("STREAM"); break;
  case VT_STORAGE: strDesc = _T("STORAGE"); break;
  case VT_STREAMED_OBJECT: strDesc = _T("STREAMED_OBJECT"); break;
  case VT_STORED_OBJECT: strDesc = _T("STORED_OBJECT"); break;
  case VT_BLOB_OBJECT: strDesc = _T("BLOB_OBJECT"); break;
  case VT_CF: strDesc = _T("CF"); break;
  case VT_CLSID: strDesc = _T("CLSID"); break;
  default: strDesc.Format(_T("[Unknown type = %d]"), vt);
  }
}

Пробуем

Попробуем получить описание всех методов для всех интерфейсов некоторой библиотеки типов. Предполагается, что в переменной strFilePath содержится путь к файлу. Информация о параметрах методов выводится в виде кодов, применяющихся для определения VARTYPE (смотри описание этого типа или констант VT_XXX в MSDN).

Листинг 3. Получение описаний методов интерфейсов библиотеки типов.
ITypeLib *iLib = NULL;
CComBSTR oFile(strFilePath);

// Пробуем получить информацию о библиотеке типовif (FAILED(LoadTypeLib(oFile, &iLib)))
{
  MessageBox(_T("\" ") +  strFilePath
    + _T(" \"\ncontains no registered type library."), _T("Error"),
    MB_OK | MB_ICONWARNING);
  return;
}

UINT n, m, k, nCount = iLib->GetTypeInfoCount();
ITypeInfo *iInfo = NULL;
TYPEATTR *pTypeAttr;
FUNCDESC *pFuncInfo;
CString strFmt, strNode;
BSTR strName;
HTREEITEM hNode, hFunction;

// Для каждого объектаfor (n = 0; n < nCount; n++)
{
  // Получаем информацию об объектеif (FAILED(iLib->GetTypeInfo(n, &iInfo)))
    continue;

  if (FAILED(iInfo->GetTypeAttr(&pTypeAttr)))
  {
    iInfo->Release();
    continue;
  }

  // Получаем имя объекта
  iLib->GetDocumentation(n, &strName, NULL, NULL, NULL);
  strNode = CString(strName);
  SysFreeString(strName);

  // Получаем GUID объекта
  CComBSTR strGuid(pTypeAttr->guid);
  strGuid.ToUpper();
  strNode += _T(" ") + CString(strGuid);

  // Определяем тип объектаswitch (pTypeAttr->typekind)
  {
  case TKIND_ENUM: strFmt = _T("Enumeration"); break;
  case TKIND_RECORD: strFmt = _T("Record"); break;
  case TKIND_MODULE: strFmt = _T("Module"); break;
  case TKIND_INTERFACE: strFmt = _T("Interface"); break;
  case TKIND_DISPATCH: strFmt = _T("Dispatch interface"); break;
  case TKIND_COCLASS: strFmt = _T("Coclass"); break;
  case TKIND_ALIAS: strFmt = _T("Alias"); break;
  case TKIND_UNION: strFmt = _T("Union"); break;
  default: strFmt = _T("*** Unknown type ***");
  }

  strNode = strFmt + _T(" ") + strNode;
  // Выводим информацию об объекте
  cout << strNode << endl;

  // Если это интерфейсif (pTypeAttr->typekind == TKIND_INTERFACE ||
    pTypeAttr->typekind == TKIND_DISPATCH)
  {
    // То для каждого методаfor (m = 0; m < (UINT) pTypeAttr->cFuncs; m++)
    {
      // Получаем информацию о методеif (FAILED(iInfo->GetFuncDesc(m, &pFuncInfo)))
        break;

      // Получаем название метода
      iInfo->GetDocumentation(pFuncInfo->memid, &strName,
        NULL, NULL, NULL);
      strNode = CString(strName);
      SysFreeString(strName);

      // Определяем тип методаswitch (pFuncInfo->invkind)
      {
      case INVOKE_FUNC: strFmt = _T("Function"); break;
      case INVOKE_PROPERTYGET:
        strFmt = _T("Property access");
      break;
      case INVOKE_PROPERTYPUT:
        strFmt = _T("Property assign");
      break;
      case INVOKE_PROPERTYPUTREF:
        strFmt = _T("Property assign by reference");
      break;
      default: strFmt = _T("*** Unknown function type ***");
      }

      strNode = _T("\t") + strFmt + _T(": \" ") + strNode + _T(" \"");
      // Выводим информацию о методе
      cout << strNode << endl;
        
      // Определяем тип возврата метода
      strNode.Format(_T("\t\tReturn type: %d"),
        pFuncInfo->elemdescFunc.tdesc.vt);
      // Выводим тип возврата
      cout << strNode << endl;

      // Для каждого параметра метода...for (k = 0; k < (UINT) pFuncInfo->cParams; k++)
      {
        // ...получаем информацию о параметре
        strNode.Format(_T("\t\tParameter %d type: %d"),
          k + 1,
          pFuncInfo->lprgelemdescParam[k].tdesc.vt);
        // ...выводим информацию о параметре
        cout << strNode << endl;
      }
      
      iInfo->ReleaseFuncDesc(pFuncInfo);
    }
  }

  iInfo->ReleaseTypeAttr(pTypeAttr);
  iInfo->Release();
}

iLib->Release();

Если немного улучшить предложенный код (смотри демонстрационный проект), можно получить полноценную информацию о методах COM-интерфейсов (на рисунке 2 приведен пример для интерфейсов Microsoft Access):


Рисунок 2.

Итого

Информация, получаемая из библиотеки типов, не является исчерпывающей. COM-объект может вообще не иметь библиотеки типов. Кроме того, в библиотеке типов может быть описана только часть реализуемых COM-объектом интерфейсов. Так, большинство элементов управления ActiveX описывает в библиотеке типов только так называемые пользовательские интерфейсы (интерфейсы, которые в основном и использует прикладной программист). В то же время о множестве интерфейсов, реализованных в этом элементе управления, никакой информации в библиотеке типов не содержится. Так что статья Павла Блудова “Known IUnknown” не теряет своей актуальности.

Но при наличии библиотек типов описанный способ - самый простой, быстрый и точный.

Литература

  1. Guy Eddon, Henry Eddon. Inside Distributed COM: Type Libraries and Language Integration. (MSDN и www.Fatbrain.com)
  2. MSDN, описание интерфейсов ITypeInfo и ITypeLib.
  3. Трельсен Э. Модель COM и применение ATL 3.0: Пер. с англ. – СПб.: БХВ-Петербург, 2001.


Эта статья опубликована в журнале RSDN Magazine #3-2003. Информацию о журнале можно найти здесь
    Сообщений 10    Оценка 265        Оценить