Сообщений 10 Оценка 265 Оценить |
Введение Смотрим MSDN Пробуем Итого Литература |
На RSDN есть статья Павла Блудова “Known IUnknown”, в которой он рассуждает о получении информации об интерфейсах COM-объекта и методах COM-интерфейсов. Предложенные им подходы (перебор всех возможных GUID, сканирование реестра, разбор pdb-файлов и т.д.) являются, на мой взгляд,… э… гм… несколько экзотичными. Существуют более удобные стандартные методы.
Для получения информации о COM-объектах существуют специальные утилиты (смотри хотя бы входящий в состав Visual Studio «OLE/COM Object Viewer»), которые и расскажут, и покажут, и IDL напишут. Но иногда необходимо получать эти данные «на лету», во время выполнения программы. Механизмам получения информации об интерфейсах и посвящена эта статья.
Информация о COM-объектах содержится в библиотеке типов (type library) – двоичном эквиваленте описания объектов на языке IDL, при условии, что таковая имеется. Обычно это файл с расширением .tlb. Кроме того, библиотека типов очень часто содержится в ресурсах исполняемого файла или dll, содержащих код COM-сервера (.exe, .dll и .olb).
Для доступа к этим данным используются два интерфейса: ITypeLib и ITypeInfo.
Для получения информации из библиотеки типов используется интерфейс ITypeLib. Он позволяет получить общую информацию о библиотеке типов, а также предоставляет интерфейс ITypeInfo для описания каждого из перечисленных объектов (см. рисунок 1).
Рисунок 1.
Получить интерфейс ITypeLib можно с помощью функций LoadTypeLib и RegisterTypeLib. Кроме того, если кокласс поддерживает интерфейс IProvideClassInfo, то с его помощью также можно получить ITypeLib. Расширенная версия интерфейса IProvideClassInfo – IProvideClassInfo2 позволяет сразу получить 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), а также предоставляет некоторые другие возможности.
Для получения информации об объектах, описанных в библиотеке типов (интерфейсах, 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. |
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; } |
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” не теряет своей актуальности.
Но при наличии библиотек типов описанный способ - самый простой, быстрый и точный.
Сообщений 10 Оценка 265 Оценить |