3 кита COM. Кит первый: реестр

Дата публикации 2 дек 2006

3 кита COM. Кит первый: реестр — Архив WASM.RU

Скачать материалы для статьи

Эта статья была написана несколько лет назад. Может, она так и осталась бы валяться в архиве на CD среди прочего хлама, если бы я недавно не наткнулся на нее, разбирая старые материалы. Хотя задумывалось это в свое время как серия из трех статей, а в наличии пока только две (и я не знаю, сподвигнусь ли завершить эту серию), и даже несмотря на высказываемое некоторыми мнение, что "COM-де нынче устарел, а модно .NET", при перечитывании мне самому стало интересно, а вместе с тем и жалко, что такой материал пропадает - раз уж он был в свое время создан, я решил сделать его доступным для всех желающих, если такие найдутся.

Технология COM для многих программистов так и остается желанным, но слишком дорогим и поэтому недоступным ресурсом. Лишь беглый взгляд на все это нагромождение объектов, интерфейсов, правил их взаимодействия, отягощенный способами их реализации на том или ином языке и громоздкими вспомогательными конструкциями тех или иных сред разработки, призванными "облегчить" использование COM, способен надолго отбить охоту заниматься этим вопросом. А тот счастливчик, кому все же удалось продраться сквозь эти дебри и который вроде начал использовать объекты COM в своих программах, зачастую оказывается совершенно беспомощным вне своей привычной среды программирования.

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

Предполагается, что читатель уже знаком или хотя бы слышал об основных концепциях COM. Здесь не будут описываться модели компонентного программирования, принципы инкапсуляции, полиморфизма, наследования и т.п. - разве что они будут упомянуты мимоходом. Вместо этого будет предложен подход с точки зрения низкоуровневого программирования, не отягощенного теоретическими абстракциями - своего рода "вид снизу" на архитектуру COM. Возможно, больше всего эти статьи подойдут тем, кто не раз начинал и бросал изучение COM, не справившись и изобилием разнородной информации, но все же не оставил желания разобраться в существе и механизмах этой технологии, а также тем, кто работает с COM на высоком уровне, но хотел бы заглянуть "за кулисы".

В качестве примера рассматриваются несколько утилит, созданных с использованием пакета MASM32. Предполагается, что читатель умеет работать с ним - вопросы создания графических Win32-приложений здесь вообще не будут рассматриваться. (Желающие могут обратиться к обучалкам Iczelion'а; в частности, хорошенько просмотрите раздел о диалоговых окнах - главы 10 и 11.) Более того, исходный код содержит некоторую мешанину - высокоуровневые конструкции MASM (.IF-.ELSE, INVOKE и т.д.) используются для "обыкновенного" каркаса Win32-приложения, а элементы COM реализованы на самом низком уровне - например, в качестве виртуальных таблиц используются даже не структуры (не говоря уже о макросах), а ряд последовательных значений. Это сделано намеренно, чтобы акцентировать внимание именно на имеющих отношение к COM деталях и дать возможность "почувствовать" их и "пощупать руками", по возможности не зацикливаясь на Win32-программировании вообще.

COM и реестр

Ну что ж, пора покончить со вступлениями и перейти к нашим китам. Одна из важнейших основ, которая активно используется и без которой невозможна сама инфраструктура COM, - это системная база данных. На платформе Windows в качестве таковой выступает системный реестр. Прежде чем двинуться дальше, придется все же сказать пару слов предупреждения.

Здесь будут описаны эксперименты с изменениями значений в реестре. Следует помнить, что эти операции нужно производить очень аккуратно, поскольку они чреваты большими неприятностями. При порче реестра компьютер перестанет загружаться, и информация на диске может оказаться утраченной. Поэтому настоятельно рекомендуется перед началом манипуляций с реестром создать аварийную загрузочную дискету (если она не была создана до этого), сделать резервную копию всех важных данных на диске (не забыв и про электронную почту с адресной книгой), а также скопировать файлы реестра (для Win9* - USER.DAT и SYSTEM.DAT в каталоге Windows) в отдельную папку. Сложнее обстоит дело с WinNT/2k/XP, установленным в раздел NTFS. Для WinXP нужно хотя бы создать дополнительную точку восстановления.

Это лишь чрезвычайный минимум сведений; рассказ о методах восстановления реестра сам потребовал бы отдельной статьи. Существует серия книг с названиями "Реестр Windows [98, NT, 2000, XP и др.]", в которых говорится и о методах восстановления реестра; в случае необходимости следует обратиться именно к ним.

Вернемся к нашим китам. Давайте посмотрим на несколько небольших примеров. Если на вашей системе установлен MS Excel, вы можете создать такой файл с расширением .vbs и запустить его:

Код (Text):
  1. Set x = CreateObject("Excel.Application")
  2. x.Workbooks.Add
  3. x.Cells(1,1).Value = 5
  4. x.Cells(1,2).Value = 10
  5. x.Cells(1,3).Value = 15
  6. x.Range("A1:C1").Select
  7. Set ch = x.Charts.Add()
  8. x.Visible = True
  9. ch.Type = -4100
  10.  

Если установлен Word XP, то можно запустить другой файл .vbs:

Код (Text):
  1. Set w = CreateObject("Word.Application")
  2. w.Visible = True
  3. Set rng = w.Document.Add.Range(0,0)
  4. With rng
  5.     .InsertBefore "Текст для WordXP"
  6.     .ParagraphFormat.Alignment = 1
  7.     With .Font
  8.         .Name = "Arial"
  9.         .Size = 16
  10.         .Color = 200
  11.     End With
  12. End With
  13.  

Если при установке MS Office был установлен элемент управления "Календарь", можно создать и просмотресть в браузере такой html-файл:

Код (Text):
  1. &ltHTML&gt
  2. &ltBODY&gt
  3. &ltOBJECT CLASSID="CLSID:8E27C92B-1264-101C-8A2F-040224009C02"&gt
  4. &lt/OBJECT&gt
  5. &lt/BODY&gt
  6. &lt/HTML&gt
  7.  

[Кстати, если этот элемент управления действительно установлен на вашем компьютере, вы можете увидеть его здесь под этим абзацем, если читаете статью в IE ]

Вы, наверное, обратили внимание на постоянное повторение "если". Это неотъемлемое свойство компонентного ПО: к сожалению, никогда нельзя быть на 100% уверенным в том, что нужный компонент будет присутствовать на платформе пользователя. К тому же, простого копирования файлов для запусков компонентов недостаточно. Программы должны быть установлены либо с использованием setup, либо отдельной регистрацией компонентов или созданием соответствующих записей в реестре вручную.

После этого становится возможным обращение к компонентам по именам. В нашем случае это были соответственно "Excel.Application", "Word.Application" и "CLSID:8E27C92B-1264-101C-8A2F-040224009C02". Вся эта и другая относящаяся к COM/OLE/ActiveX информация хранится в разделе реестра "HKEY_LOCAL_MACHINE\Software\CLASSES". Этот раздел настолько важен, что для него был создан алиас под видом отдельного корневого раздела реестра "HKEY_CLASSES_ROOT".

Рис. 1. Редактор реестра.
Рис. 1. Редактор реестра.

Структура информации о COM в реестре

Теперь самое время запустить редактор реестра (набрав в командной строке "regedit"; см. рис. 1) и начать знакомиться с рассматриваемыми вопросами на практике. Относящиеся к COM данные объединяются в 5 групп, для большинства из которых предусмотрены соответствующие разделы реестра: ProgID, CLSID, TypeLib, Interface, AppID. В качестве раздела для ProgID выступает сам корневой раздел "HKEY_CLASSES_ROOT". Вот здесь-то мы и можем обнаружить те самые имена "Excel.Application", "Word.Application", которые мы использовали ранее в скриптах, наряду с расширениями файлов и оставшимися 4 разделами для COM.

ProgID - это так называемый программный идентификатор, он является на самом деле лишь "дружественной для пользователя" меткой "настоящего" имени компонента - CLSID. Считается, что ProgID не способен обеспечить подлинной глобальной уникальности имени, особенно в распределенной межсетевой среде, поэтому в последнем случае в качестве идентификаторов компонентов должны использоваться исключительно CLSID. Но на локальной машине, особенно в скриптах, ProgID широко используется. Однако в любом случае для идентификации компонента используется его CLSID; для этой цели между ключами CLSID и ProgID компонента установлены перекрестные ссылки.

Рассмотрим, например, ключ "Excel.Application". Он имеет подключ CLSID, значение по умолчанию которого и является искомым CLSID в строковом представлении. Это значит, что в разделе "HKEY_CLASSES_ROOT\CLSID" имеется соответствующий ключ, содержащий всю необходимую для загрузки компонента информацию. Наверняка вы обратили внимание и на то, что рядом с ключом "Excel.Application" имеется еще один такой же ключ, только с цифрой в конце (на разных машинах она может быть разной; у меня, например, "Excel.Application.5"); а сам ключ содержит также другой подключ - CurVer. На самом деле именно "Excel.Application.5" и является ProgID, а "Excel.Application" называется VersionIndependentProgID - "независимый от версии идентификатор прогаммы" (именно такие подключи для перекрестных ссылок вы найдете в разделе "HKEY_CLASSES_ROOT\CLSID" для соотвествующего компонента). Таким образом, в скрипте можно не указывать конкретную версию установленного компонента; а если со временем был сделан апгрейд, старые скрипты будут успешно работать с новыми версиями, если в качестве имен объектов они использовали VersionIndependentProgID. Для получения одного ключа из другого система COM предоставляет вспомогательные функции API CLSIDFromProgID и ProgIDFromCLSID.

Чтобы получше впитать в себя эту теорию, можно слегка похулиганить (не забывая, однако, сделанного ранее предупреждения). Убедимся, что "настоящее" имя компонента - это действительно CLSID. Для этого создадим новый ключ в разделе "HKEY_CLASSES_ROOT" и назовем его для разнообразия, скажем, собственным именем. Затем под этим ключем создаем подключ с именем "CLSID". Скопируем значение подключа CLSID для ProgID "Excel.Application" и вставим его в качестве значения по умолчанию нашего вновь созданного CLSID. А теперь слегка перепишем наш скрипт:

Код (Text):
  1. Set x = CreateObject("MyName")
  2. x.Visible = True
  3.  

Естественно, вместо "MyName" у вас должно быть то имя, которое вы выбрали для своего ProgID. Если все было сделано правильно, теперь Excel послушно "откликается" на новое имя.

Утилита для ProgID

Копаясь в реестре, можно обнаружить множество интересных вещей. Однако, ручной поиск по перекрестным ссылкам, особенно если компонентов очень много, весьма утомителен. Сочетая приятное с полезным, создадим для наших поисков небольшую утилиту, которая будет переводить данный ProgID компонента в его CLSID и наоборот.

Рис. 2. Интерфейс утилиты COM_1
Рис.2. Интерфейс утилиты COM_1

Дизайн пользовательского интерфейса утилиты приведен на рис. 2. Он реализуется следующим файлом ресурсов (COM_1.rc):

Код (Text):
  1. #define DS_MODALFRAME       0x80L
  2. #define DS_CENTER           0x0800L
  3. #define WS_POPUP            0x80000000L
  4. #define WS_CAPTION          0x00C00000L
  5. #define WS_SYSMENU          0x00080000L
  6. #define ES_AUTOHSCROLL      0x0080L
  7.  
  8. #define IDC_EDIT1           1000
  9. #define IDC_EDIT2           1001
  10. #define IDB_ProgID          1002
  11. #define IDB_CLSID           1003
  12. #define IDC_STATIC          -1
  13.  
  14. MyDialog DIALOG DISCARDABLE  200, 200, 200, 66
  15. STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
  16. CAPTION "ProgID <-> CLSID"
  17. FONT 8, "MS Sans Serif"
  18. BEGIN
  19.     DEFPUSHBUTTON   "Get ProgID",IDB_ProgID,35,46,50,14
  20.     PUSHBUTTON      "Get CLSID",IDB_CLSID,108,46,50,14
  21.     LTEXT           "CLSID:",IDC_STATIC,7,7,26,12
  22.     LTEXT           "ProgID:",IDC_STATIC,7,27,26,12
  23.     EDITTEXT        IDC_EDIT1,38,7,154,12,ES_AUTOHSCROLL
  24.     EDITTEXT        IDC_EDIT2,38,27,154,12,ES_AUTOHSCROLL
  25. END
  26.  

Исходный код программы содержится в файле COM_1.asm. Начало стандартное; функции COM API содержатся в OLE32.dll, поэтому необходимо включить файлы для поддержки этого модуля.

Код (Text):
  1. .386
  2. .model flat,stdcall
  3. option casemap:none
  4.  
  5. DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
  6.  
  7. include \masm32\include\windows.inc
  8. include \masm32\include\user32.inc
  9. include \masm32\include\kernel32.inc
  10. include \masm32\include\ole32.inc
  11. includelib \masm32\lib\user32.lib
  12. includelib \masm32\lib\kernel32.lib
  13. includelib \masm32\lib\ole32.lib
  14.  
  15. .data
  16.  
  17. DlgName db "MyDialog",0
  18. App     db "ProgID_CLSID",0
  19. ms1     db "Enter CLSID here",0
  20. ms2     db "Enter ProgID here",0
  21. ms3     db "There is no ProgID for this CLSID",0
  22. ms4     db "There is no CLSID for this ProgID",0
  23. Err1        db "The wrong CLSID",0
  24.  
  25. .data?
  26.  
  27. hInstance   HINSTANCE ?
  28. hEdit1  DWORD ?     ; окно редактирования для CLSID
  29. hEdit2  DWORD ?     ; окно редактирования для ProgID
  30. buf     db 256 dup(?)   ; для строк ANSI
  31. wbuf        db 512 dup(?)   ; для строк Unicode
  32. cls     db 16 dup(?)    ; буфер для CLSID
  33. bufaddr DWORD ?
  34.  
  35. .const
  36.  
  37. IDC_EDIT1   equ 1000    ; окно редактирования для CLSID
  38. IDC_EDIT2   equ 1001    ; окно редактирования для ProgID
  39. IDB_ProgID  equ 1002    ; кнопка "Get ProgID"
  40. IDB_CLSID   equ 1003    ; кнопка "Get CLSID"
  41.  

Утилита реализована в виде модального диалогового окна, создаваемого из шаблона ресурса:

Код (Text):
  1. .code
  2.  
  3. start:
  4. invoke GetModuleHandle, NULL
  5. mov    hInstance,eax
  6. invoke DialogBoxParam, hInstance, ADDR DlgName,NULL,
  7. addr  DlgProc, NULL
  8. invoke ExitProcess,eax
  9.  
  10. DlgProc proc USES esi edi ebx,hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  11. .IF uMsg==WM_INITDIALOG
  12.     ; сохранить описатели для окон редактирования
  13.     invoke GetDlgItem,hWnd,IDC_EDIT1
  14.     mov hEdit1,eax
  15.     invoke GetDlgItem,hWnd,IDC_EDIT2
  16.     mov hEdit2,eax
  17.     invoke SetFocus,hEdit1
  18.     mov eax,FALSE
  19.     ret
  20. .ELSEIF uMsg==WM_CLOSE
  21.     invoke EndDialog,hWnd,0
  22.  

Алгоритм работы следующий. При нажатии кнопки "Get ProgID" окно для ProgID очищается и считывается текст окна для CLSID.

Код (Text):
  1. .ELSEIF uMsg==WM_COMMAND
  2.     mov eax,wParam
  3.     mov edx,wParam
  4.     shr edx,16
  5.     .if dx==BN_CLICKED
  6.         .if ax==IDB_ProgID  ; преобразовать CLSID в ProgID
  7.             mov buf,0
  8.             invoke SetWindowText,hEdit2,ADDR buf
  9.             invoke GetWindowText,hEdit1,ADDR buf,255
  10.  

Если текста нет - вывести сообщение с предложением его ввести:

Код (Text):
  1. .if eax==0  ; GetWindowText - нет текста
  2.     invoke MessageBox,0,ADDR ms1,ADDR App,0
  3.     invoke SendMessage,hEdit1,EM_SETSEL,0,-1
  4.     invoke SetFocus,hEdit1
  5. .else       ; текст получен
  6.  

Здесь нас ожидает небольшой сюрприз - в COM используются строки в формате Unicode. Поэтому придется преобразовать текст в буфере buf с помощью функции MultiByteToWideChar. Эта функция принимает 6 параметров:

  • кодовая страница преобразуемого текста (в нашем случае - ANSI, параметр CP_ACP);
  • флаги, указывающие на способ преобразования сложных композитных символов; нам это не требуется - оставляем 0;
  • адрес буфера с преобразуемой строкой;
  • число символов в преобразуемой строке; если -1, строка заканчивается нулем и длина строки определяется автоматически;
  • адрес буфера для преобразованной строки;
  • размер буфера для преобразованной строки.

Код (Text):
  1.     invoke MultiByteToWideChar,CP_ACP,0,ADDR buf,-1,ADDR wbuf,510

Еще одно затруднение. Функция ProgIDFromCLSID принимает в качестве аргумента адрес структуры с числовым представлением CLSID, а не с его строковым представлением. Подробнее этот нюанс мы обсудим в разделе о CLSID, а сейчас используем для преобразования строкового представления в числовой еще одну вспомогательную функцию COM API - CLSIDFromString (где cls - оставленный под структуру CLSID буфер размером 16 байт):

Код (Text):
  1.     invoke CLSIDFromString,ADDR wbuf,ADDR cls
  2.     .if eax==NOERROR
  3.         invoke ProgIDFromCLSID,ADDR cls,ADDR bufaddr
  4.         .if eax==S_OK
  5.  

Мы получили в bufaddr строковое представление ProgID в формате Unicode, теперь нужно преобразовать его обратно в ANSI с помощью функции WideCharToMultiByte ("напарницей" MultiByteToWideChar). Она принимает 8 аргументов:

  • кодовая страница, в которую нужно преобразовать строку Unicode; в нашем случае - снова CP_ACP;
  • флаги, указывающие способ обработки неотображаемых символов. Нам это не требуется, оставляем 0;
  • адрес строки Unicode;
  • число символов в строке Uinicode. Если -1, строка считается завершающейся нулями и ее длина определяется автоматически;
  • адрес буфера для преобразованной строки;
  • размер буфера для преобразованной строки;
  • символ по умолчанию - нам не нужно, оставляем 0;
  • флаги, указывающие на способ использования символа по умолчанию - 0.

Код (Text):
  1.         invoke WideCharToMultiByte,CP_ACP,0,bufaddr,-1,ADDR buf,255,0,0
  2.         invoke SetWindowText,hEdit2,ADDR buf
  3.     .else   ; не удалось преобразовать CLSID в ProgID -
  4.         ; выдать соответствующее сообщение
  5.         invoke SetWindowText,hEdit2,ADDR ms3
  6.     .endif
  7.  

Дальше - блок обработки ошибки функции CLSIDFromString, что рассматривается как неправильный формат введенной строки CLSID:

Код (Text):
  1.     .else   ; ошибка CLSIDFromString
  2.         invoke MessageBox,0,ADDR Err1,ADDR App,MB_OK OR MB_ICONERROR
  3.         invoke SendMessage,hEdit1,EM_SETSEL,0,-1
  4.         invoke SetFocus,hEdit1
  5.     .endif  ;NOERROR - CLSIDFromString
  6. .endif  ;GetWindowText
  7.  

Блок обработки нажатия на кнопку "Get ProgID" завершен. Сходным же образом обрабатывается нажатие кнопки "Get CLSID".

Код (Text):
  1. .elseif ax==IDB_CLSID   ;преобразовать ProgID в строку CLSID
  2.     mov buf,0
  3.     invoke SetWindowText,hEdit1,ADDR buf
  4.     invoke GetWindowText,hEdit2,ADDR buf,255
  5.     .if eax==0  ; нет текста в окне - сообщение об ошибке
  6.         invoke MessageBox,0,ADDR ms2,ADDR App,0
  7.         invoke SendMessage,hEdit2,EM_SETSEL,0,-1
  8.         invoke SetFocus,hEdit2
  9.     .else       ; текст ProgID получен - преобразовать в Unicode
  10.         invoke MultiByteToWideChar,CP_ACP,0,ADDR buf,-1,ADDR wbuf,510
  11.             ; преобразовать ProgID в числовую форму CLSID
  12.         invoke CLSIDFromProgID,ADDR wbuf,ADDR cls
  13.         .if eax==S_OK   ; CLSID получено,
  14.                     ; преобразовать в строковую форму
  15.             invoke StringFromCLSID,ADDR cls,ADDR bufaddr
  16.                     ; преобразовать строку Unicode в ANSI
  17.             invoke WideCharToMultiByte,CP_ACP,0,bufaddr,-1,
  18. ADDR  buf,255,0,0
  19.             invoke CoTaskMemFree,bufaddr    ; освободить память,
  20. ; выделенную при вызове StringFromCLSID
  21.             invoke SetWindowText,hEdit1,ADDR buf
  22.         .else   ; не удалось получить CLSID из ProgID
  23.             invoke SetWindowText,hEdit1,ADDR ms4
  24.         .endif  ;S_OK (CLSIDFromProgID)
  25.     .endif  ;GetWindowText
  26. .endif  ;ax==IDB_ProgID
  27.  

На этом обработка сообщений диалогового окна заканчивается:

Код (Text):
  1.         .endif  ;BN_CLICKED
  2.     .ELSE
  3.         mov eax,FALSE
  4.         ret
  5.     .ENDIF  ;uMsg
  6.        mov eax,TRUE
  7.        ret
  8. DlgProc endp
  9.  
  10. end start
  11.  

Файлы с исходным кодом данной утилиты (COM_1.rc и COM_1.asm, а также makefile) находятся в каталоге COM_1 архива ComKit1.rar. Makefile написан в предположении, что пакет MASM32 установлен в каталоге \masm32 на текущем диске; если это не так, необходимо его отредактировать.

Раздел CLSID

Вся основная информация о компонентах размещается в разделе реестра "HKEY_CLASSES_ROOT\CLSID". Для каждого компонента создается отдельный ключ, в качестве имени которого выступает строковое представление его CLSID. Как отмечалось выше, CLSID (как и другие виды GUID, например, идентификаторы интерфейсов (IID) или библиотеки типов) имеет две формы представления: числовую и строковую. Числовая форма представляет собой 128-битное значение. В принципе это значение можно было бы разместить, скажем, в регистре XMM; однако COM создавалась еще во времена 16-разрядных машин, когда ни о каких XMM-регистрах и не помышляли. Поэтому для представления этого 128-битного значения была использована структура следующего вида:

Код (Text):
  1. GUID    STRUCT
  2.     Data1   dd ?
  3.     Data2   dw ?
  4.     Data3   dw ?
  5.     Data4   db 8 dup(?)
  6. GUID ENDS
  7.  

В реестре же для представления GUID используется строка такого формата:

Код (Text):
  1. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

где х обозначает 16-ричную цифру (0-F), а фигурные скобки и дефисы должны присутствовать на своих местах без всяких разделяющих пробелов. На самом деле, эта запись может послужить источником путаницы: обратите внимание, что блок цифр для поля Data4 в строковой записи разделен на две части в 4 и 12 шестнадцатеричных цифр, и часть в 4 цифры можно спутать с предыдущими WORD-значениями. Первое DWORD и два последующих WORD значения записываются в строковом представлении, как обычные числа, т.е. старшие байты идут вначале, тогда как третий "WORD" (первые 2 байта поля Data4) записывается в порядке "little-endian" - вначале идет младший байт.

Как мы уже видели в коде первой утилиты, для преобразования строковой формы представления CLSID в числовую и обратно используются соотвтественно функции CLSIDFromString и StringFromCLSID. Существуют варианты и для других типов GUID - StringFromIID, IIDFromString, StringFromGUID2.

Ключ компонента CLSID содержит, в свою очередь, множество подключей. С двумя из них - ProgID и VersionIndependentProgID - мы уже познакомились. Стоит упомянуть лишь о том, что у компонента не обязательно должно присутствовать ProgID.

Простой универсальный клиент COM

Прежде, чем двинуться дальше в изучении раздела CLSID, нам понадобится еще одна небольшая утилита - универсальный клиент COM. Пользовательский интерфейс ее приведен на рис. 3. Этот клиент позволяет загружать объект COM, CLSID которого вводится в первое окно редактирования, и запрашивает у него указатель на интерфейс, IID которого содержится во втором окне редактирования. Тип сервера указывается набором флажков опций (одновременно может быть указано несколько типов). Кнопка IUnknown позволяет отобразить во втором окне редактирования IID часто употребляемого интерфейса с тем же названием.

Рис.3. Интерфейс утилиты COM_2
Рис.3. Интерфейс утилиты COM_2

Вот соответствующий файл ресурсов (COM_2.rc):

Код (Text):
  1. #include "\masm32\include\resource.h"
  2.  
  3. #define IDC_EDIT1       1001
  4. #define IDC_EDIT2       1002
  5. #define IDC_CHECK1  1003
  6. #define IDC_CHECK2  1004
  7. #define IDC_CHECK3  1005
  8. #define IDC_CHECK4  1006
  9. #define IDC_BUTTON1 1007
  10. #define IDC_STATIC  -1
  11.  
  12. MyDialog DIALOGEX 0, 0, 214, 90
  13. STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
  14. CAPTION "Simple COM client"
  15. FONT 8, "MS Sans Serif"
  16. BEGIN
  17.     LTEXT           "CLSID:",IDC_STATIC,7,10,26,8,0,WS_EX_RIGHT
  18.     LTEXT           "IID:",IDC_STATIC,7,28,26,8,0,WS_EX_RIGHT
  19.     GROUPBOX        "Server type:",IDC_STATIC,7,45,142,38
  20.     EDITTEXT        IDC_EDIT1,40,7,167,14,ES_AUTOHSCROLL
  21.     EDITTEXT        IDC_EDIT2,40,26,167,14,ES_AUTOHSCROLL
  22.     CONTROL         "InProc server",IDC_CHECK1,"Button",BS_AUTOCHECKBOX |
  23.                     WS_TABSTOP,15,55,58,10
  24.     CONTROL         "InProc handler",IDC_CHECK2,"Button",BS_AUTOCHECKBOX |
  25.                     WS_TABSTOP,15,68,62,10
  26.     CONTROL         "Local server",IDC_CHECK3,"Button",BS_AUTOCHECKBOX |
  27.                     WS_TABSTOP,80,55,55,10
  28.     CONTROL         "Remoute server",IDC_CHECK4,"Button",BS_AUTOCHECKBOX |
  29.                     WS_TABSTOP,80,68,66,10
  30.     PUSHBUTTON      "IUnknown",IDC_BUTTON1,157,49,50,14
  31.     DEFPUSHBUTTON   "Connect",IDOK,157,67,50,14
  32. END
  33.  

Чтобы не выписывать каждый раз стандартные define'ы, целесообразно скопировать файл "resource.h" из 10 урока обучалки Iczelion'а в каталог "Include" пакета MASM32, а затем вставлять в rc-файлы соответствующую ссылку, как это сделано в данном случае.

Реализация в файле COM_2.asm:

Код (Text):
  1. .386
  2. .model flat,stdcall
  3. option casemap:none
  4.  
  5. DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
  6.  
  7. include \masm32\include\windows.inc
  8. include \masm32\include\user32.inc
  9. include \masm32\include\kernel32.inc
  10. include \masm32\include\ole32.inc
  11. includelib \masm32\lib\user32.lib
  12. includelib \masm32\lib\kernel32.lib
  13. includelib \masm32\lib\ole32.lib
  14.  
  15. .data
  16.  
  17. DlgName db "MyDialog",0
  18. app     db "SimpleClient",0
  19. ms1     db "Enter CLSID here",0
  20. ms2     db "Enter IID here",0
  21. szUnk       db "{00000000-0000-0000-C000-000000000046}",0
  22. mscheck db "The server type must be indicated",0
  23. msclsid db "Enter CLSID here",0
  24. msiid       db "Enter IID here",0
  25. msok    db "Got interface pointer to:",13,10,"CLSID:",9,"%s",13,10,"IID:",9,"%s",0
  26. mserr       db "CoCreateInstance failed:",13,10,"HRESULT==%Xh",0
  27. err1        db "The wrong CLSID",0
  28. err2        db "The wrong IID",0
  29.  
  30. .data?
  31.  
  32. hInstance   HINSTANCE ?
  33. hEdit1  DWORD ?     ; "CLSID"
  34. hEdit2  DWORD ?     ; "IID"
  35. hCheck1 DWORD ?     ; "Inproc server"
  36. hCheck2 DWORD ?     ; "Inproc handler"
  37. hCheck3 DWORD ?     ; "Local server"
  38. hCheck4 DWORD ?     ; "Remoute server"
  39. ctx     DWORD ?     ; тип сервера
  40. szclsid db 64 dup(?)    ; буфер для строки с CLSID
  41. sziid       db 64 dup(?)    ; буфер для строки с IID
  42. buf     db 256 dup(?)   ; для ANSI строк
  43. wbuf        db 512 dup(?)   ; для строк Unicode
  44. cls     db 16 dup(?)    ; CLSID компонента
  45. iid     db 16 dup(?)    ; IID вызываемого интерфейса
  46. pUnk        DWORD ?     ; указатель на интерфейс IUnknown
  47.  
  48. .const
  49.  
  50. IDC_EDIT1   equ 1001    ; "CLSID"
  51. IDC_EDIT2   equ 1002    ; "IID"
  52. IDC_CHECK1  equ 1003    ; "Inproc server"
  53. IDC_CHECK2  equ 1004    ; "Inproc handler"
  54. IDC_CHECK3  equ 1005    ; "Local server"
  55. IDC_CHECK4  equ 1006    ; "Remoute server"
  56. IDC_BUTTON1 equ 1007    ; "IUnknown"
  57.  
  58. .code
  59.  
  60. start:
  61.     invoke GetModuleHandle, NULL
  62.     mov    hInstance,eax
  63.     invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
  64.     invoke ExitProcess,eax
  65.  
  66. DlgProc proc USES esi edi ebx,hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  67. .IF uMsg==WM_INITDIALOG
  68.     ; сохраняем описатели элементов управления
  69.     invoke GetDlgItem,hWnd,IDC_EDIT1
  70.     mov hEdit1,eax
  71.     invoke GetDlgItem,hWnd,IDC_EDIT2
  72.     mov hEdit2,eax
  73.     invoke GetDlgItem,hWnd,IDC_CHECK1
  74.     mov hCheck1,eax
  75.     invoke GetDlgItem,hWnd,IDC_CHECK2
  76.     mov hCheck2,eax
  77.     invoke GetDlgItem,hWnd,IDC_CHECK3
  78.     mov hCheck3,eax
  79.     invoke GetDlgItem,hWnd,IDC_CHECK4
  80.     mov hCheck4,eax
  81.     invoke SetFocus,hEdit1
  82.  

Поскольку на этот раз нам необходимо задействовать библиотеку COM, нужно также вызвать функцию CoInitialize. При завершении программы необходимо соответственно вызвать функцию CoUninitialize.

Код (Text):
  1.     invoke CoInitialize,0
  2.     mov eax,FALSE
  3.     ret
  4. .ELSEIF uMsg==WM_CLOSE
  5.     invoke CoUninitialize
  6.     invoke EndDialog,hWnd,0
  7.  

"Ядром" данной утилиты является использование функции CoCreateInstance, которая делает всю работу - локализует соответствующий объект COM, загружает его, запрашивает указанный интерфейс и возвращает указатель на него. Значительная часть кода обработки сообщения WM_COMMAND сводится к сбору данных для передачи в качестве параметров функции CoCreateInstance и проверке введенных значений. CoCreateInstance принимает 5 аргументов:

  • адрес структуры, содержащей CLSID запрашиваемого объекта (мы получаем это значение в первом окне редактирования);
  • указатель на так называемый внешний IUnknown, если объект агрегируется в состав другого объекта. Если агрегирование не применяется (как в нашем случае), значение аргумента равно 0;
  • контекст создания объекта: флаги, указывающие на то, является ли сервер объекта внутрипроцессным, локальным, удаленным или это внутрипроцессный обработчик (данный флаг мы вычисляем на основе проверки состояния флажков опций);
  • адрес структуры с IID интерфейса, указатель на который мы хотим получить. Значение IID мы получаем во втором окне редактирования;
  • адрес переменной, в которой будет возвращен указатель на запрашиваемый нами интерфейс (или 0, если этот интерфейс не поддерживается).

При нажатии на кнопку IUnknown во второе окно редактирования копируется IID для соответствующего интерфейса (это значение "вшито" в код в виде строки szUnk). Итак, обработка команд:

Код (Text):
  1. .ELSEIF uMsg==WM_COMMAND
  2.     mov eax,wParam
  3.     mov edx,wParam
  4.     shr edx,16
  5.     .if dx==BN_CLICKED
  6.         .if ax==IDC_BUTTON1 ; кнопка 'IUnknown'
  7. invoke SetWindowText,hEdit2,offset szUnk
  8.         .elseif ax==IDOK        ; кнопка 'Connect'
  9. ; получить CLSID
  10. invoke GetWindowText,hEdit1,offset szclsid,64
  11. .if eax==0  ; текст отсутствует - сообщение
  12.     invoke MessageBox,0,offset msclsid,offset app,MB_ICONEXCLAMATION
  13.     invoke SendMessage,hEdit1,EM_SETSEL,0,-1
  14.     invoke SetFocus,hEdit1
  15.     mov eax,TRUE
  16.     ret
  17. .else       ; текст получен - перевести в ANSI и
  18.         ; преобразовать в числовую форму, сохранив в cls
  19. invoke MultiByteToWideChar,CP_ACP,0,offset szclsid,-1,offset wbuf,510
  20.     invoke CLSIDFromString,offset wbuf,offset cls
  21.     .if eax!=NOERROR    ; ошибка CLSIDFromString рассматривается
  22.                 ; как неверный формат введенной строки с CLSID
  23.         invoke MessageBox,0,offset err1,offset app,MB_ICONERROR
  24.         invoke SendMessage,hEdit1,EM_SETSEL,0,-1
  25.         invoke SetFocus,hEdit1
  26.         mov eax,TRUE
  27.         ret
  28.     .endif  ;NOERROR - CLSIDFromString
  29. .endif  ; GetWindowText (hEdit1)
  30.  
  31. ; получить IID
  32. invoke GetWindowText,hEdit2,offset sziid,64
  33. .if eax==0  ; текста нет - сообщение
  34.     invoke MessageBox,0,offset msiid,offset app,MB_ICONEXCLAMATION
  35.     invoke SendMessage,hEdit2,EM_SETSEL,0,-1
  36.     invoke SetFocus,hEdit2
  37.     mov eax,TRUE
  38.     ret
  39. .else   ; текст с IID получен, преобразовать в ANSI,
  40.     ; затем в числовую форму, сохранив ее в iid
  41.     invoke MultiByteToWideChar,CP_ACP,0,offset sziid,-1,offset wbuf,510
  42.     invoke IIDFromString,offset wbuf,offset iid
  43.     .if eax!=NOERROR    ; ошибка IIDFromString рассматривается
  44.                 ; как неверный формат введенной строки с IID
  45.         invoke MessageBox,0,offset err2,offset app,MB_ICONERROR
  46.         invoke SendMessage,hEdit2,EM_SETSEL,0,-1
  47.         invoke SetFocus,hEdit2
  48.         mov eax,TRUE
  49.         ret
  50.     .endif  ;NOERROR - IIDFromString
  51. .endif  ;GetWindowText (hEdit2)
  52.  
  53. ; проверить состояние флажков опций и определить тип сервера,
  54. ; сохранив полученное значение в ctx
  55. mov ctx,0
  56. invoke IsDlgButtonChecked,hWnd,IDC_CHECK1
  57. .if eax==BST_CHECKED
  58.     or ctx,CLSCTX_INPROC_SERVER
  59. .endif
  60. invoke IsDlgButtonChecked,hWnd,IDC_CHECK2
  61. .if eax==BST_CHECKED
  62.     or ctx,CLSCTX_INPROC_HANDLER
  63. .endif
  64. invoke IsDlgButtonChecked,hWnd,IDC_CHECK3
  65. .if eax==BST_CHECKED
  66.     or ctx,CLSCTX_LOCAL_SERVER
  67. .endif
  68. invoke IsDlgButtonChecked,hWnd,IDC_CHECK4
  69. .if eax==BST_CHECKED
  70.     or ctx,CLSCTX_REMOTE_SERVER
  71. .endif
  72. .if ctx==0  ; не выбран ни один тип сервера - сообщение
  73.     invoke MessageBox,0,offset mscheck,offset app,MB_ICONEXCLAMATION
  74.     invoke SetFocus,hCheck1
  75.     mov eax,TRUE
  76.     ret
  77. .endif
  78.  

Теперь необходимые параметры собраны: CLSID находится в cls, IID - в iid, тип сервера - в ctx. Функция CoCreateInstance находит объект COM с данным CLSID и создает его экземпляр, запрашивая у него указатель на интерфейс IID. Если объект реализует интерфейс с данным IID, он помещает указатель на него в переменную, адрес которой был передан CoCreateInstance в последнем параметре, а сама функция возвращает S_OK. Если объект не поддерживает данный интерфейс или объект с данным CLSID не зарегистрирован в реестре, или зарегистрированный тип сервера не указан в флаге типа сервера, возвращается сообщение об ошибке.

Код (Text):
  1. invoke CoCreateInstance,offset cls,0,ctx,offset iid,offset pUnk
  2. mov ecx,eax
  3. .if eax==S_OK   ; Объект успешно создан и указатель на затребованный
  4.             ; интерфейс возвращен в pUnk. Выводим сообщение с
  5.             ; указанием CLSID объекта и IID интерфейса
  6.     invoke wsprintf,offset buf,offset msok,offset szclsid,offset sziid
  7.     invoke MessageBox,0,offset buf,offset app,MB_ICONINFORMATION
  8.  

На этом вся "полезная" работа нашего приложения завершается - мы освобождаем объект. Делать это придется уже средствами COM, т.е. с использованием полученного указателя интерфейса. Для этого через полученный указатель интерфейса (pUnk) необходимо вызвать метод Release интерфейса IUnknown (абсолютно все интерфейсы COM наследуются от IUnknown, поэтому метод Release можно вызвать для любого объекта COM одним и тем же способом).

Указатель на интерфейс объекта представляет собой адрес некоей структуры, в которой содержатся данные с состоянием экземпляра объекта. Доступ к ним возможен лишь косвенно, посредством вызова методов соответствующего интерфейса, поэтому каждый метод получает в качестве первого параметра адрес этой структуры (т.н. указатель "this", в терминологии языка С++). Об этой структуре можно сказать лишь то, что первое ее поле является указателем на другую структуру - "виртуальную таблицу", содержащую адреса процедур, реализующих методы интерфейса. Для вызова того или иного метода необходимо найти адрес его реализации по заранее известному и постоянному смещению в виртуальной таблице. Подробнее "кухню" этого вопроса мы разберем во второй статье, а пока просто приведем код:

Код (Text):
  1.     mov eax,pUnk
  2.     push eax        ; указатель на экземпляр объекта ("this")
  3.     mov eax,[eax]   ; получаем указатель на виртуальную таблицу,
  4.     call dword ptr [eax+8]  ; затем - указатель на третью функцию
  5. ; в таблице (Release) и вызываем ее
  6.  
  7. .else   ; ошибка CoCreateInstance
  8.     invoke wsprintf,offset buf,offset mserr,ecx
  9.     invoke MessageBox,0,offset buf,offset app,MB_ICONERROR
  10. .endif  ; eax==S_OK (CoCreateInstance)
  11.  

На этом обработка WM_COMMAND и любых других сообщений закончена.

Код (Text):
  1.             .endif  ;ax==IDOK
  2.  
  3.         .endif  ;BN_CLICKED
  4.  
  5.     .ELSE   ; uMsg не обрабатывается
  6.         mov eax,FALSE
  7.         ret
  8.     .ENDIF  ; uMsg
  9.        mov eax,TRUE
  10.        ret
  11. DlgProc endp
  12.  
  13. end start
  14.  

Подключи раздела CLSID

Теперь можно продолжить эксперименты с реестром. Важнейшими подключами раздела "HKEY_CLASSES_ROOT\CLSID" является ряд подключей, позволяющих определить местонахождение сервера в системе: InprocServer, InprocServer32, InprocHandler, InprocHandler32, LocalServer, LocalServer32. Каждый тип сервера представлен двумя подключами; суффикс "32" означает, что сервер 32-разрядный, подключ без этого суффикса указывает на то, что сервер 16-разрядный (для совместимости со старыми компонентами). Случай удаленного (remoute) сервера обрабатывается особым образом; об этом мы поговорим в разделе, посвященном AppID.

Начнем хулиганить. При создании новых интерфейсов и компонентов для получения уникальных GUID используются специальные утилиты типа guidgen и т.п. Microsoft зарезервировала для собственного употребления большой интервал удобных GUID-ов с наборами нулей; мы же поступим еще круче - используем GUID'ы типа {00000000-0000-0000-0000-000000000001}. Зачем нам париться, выискивая свой компонент среди массы цифр, когда можно удобно разместить его в самом начале раздела? Можно было бы (и вначале автор так и поступал) использовать и GUID со всеми нулями, но это специальный зарезервированный GUID_NULL, употребляющийся в особых случаях; во избежание неприятностей лучше оставить его в покое.

Создадим в разделе "HKEY_CLASSES_ROOT\CLSID" ключ {00000000-0000-0000-0000-000000000001}. Этот ключ может иметь значение по умолчанию, которое является описанием компонента, предназначенным для пользователя. Обратите внимание, это не то же самое, что ProgID (хотя некоторые авторы компонентов используют в качестве описания ProgID). Можно вставить сюда какую-нибудь строку, например, "Мой любимый компонент". Далее, создаем подключ с именем "LocalServer32". А вот значение по умолчанию этого подключа как раз и содержит полный путь к файлу сервера. Запишем сюда "c:\windows\calc.exe" (нужно указать каталог windows на вашей машине). Теперь запустим наш клиент (COM_2.exe), наберем CLSID, нажмем на IUnknown, в качестве сервера выбираем "Local server", жмем "Connect" и... Калькулятор можно закрыть. А наш клиент, похоже, завис - но к этому надо было быть готовым, поскольку калькулятор понятия не имеет о том, что он вдруг стал компонентом COM; тогда как система COM все еще дожидается от него ответа - наш клиент в это время "сидит внутри" CoCreateInstance. Впрочем, примерно через минуту она вернет ошибку таймаута, и наш клиент выдаст соответствующее окно сообщения.

Я не буду описывать здесь все прочие эксперименты - путь указан, утилиты есть; экспериментируйте! Технология COM постигается лишь на практике. Только не забудьте в пылу увлечения предупреждение об аккуратности работы с реестром.

Рассмотрим другие подключи. Важными являются TreatAs и AutoTreatAs, поскольку они позволяют сменить сервер компонента. Если в ключе с данным CLSID имеется подключ TreatAs (значение по умолчанию которого содержит другой CLSID), то будет создан объект с этим другим CLSID, независимо от наличия подключей InprocServer, LocalServer и т.д. Проделаем такой эксперимент. Создадим подключ "TreatAs" в нашем ключе {00000000-0000-0000-0000-000000000001}. В качестве значения вставим CLSID для компонента Excel.Application (воспользовавшись для преобразования ProgID утилитой COM_1.exe). Если теперь с помощью COM_2.exe попытаться загрузить наш компонент, будет загружен Excel (правда, он останется скрытым. Чтобы убедиться, что он запущен, придется посмотреть в менеджере задач).

Подключ TreatAs позволяет осуществлять эмуляцию одного сервера другим. Функция CoGetTreatAsClass позволяет получить значение этого ключа, а функция CoTreatAsClass - установить (или удалить) его. Если подключ TreatAs удаляется с помощью функции CoTreatAsClass, эта функция проверяет значение подключа AutoTreatAs. Если ключ с таким именем существует, его значение копируется в подключ TreatAs; если нет, подключ TreatAs вообще удаляется. Подключ же AutoTreatAs можно создавать или удалять только вручную, с использованием функций API реестра. Таким образом, в подключе AutoTreatAs может сохраняться значение предыдущего CLSID, если данный сервер эмулируется сначала одним, а затем другим сервером, обеспечивая своего рода "постоянную" эмуляцию (в отличие от "временной" эмуляции с помощью подключа TreatAs).

Будучи основным источником сведений о компоненте, раздел "HKEY_CLASSES_ROOT\CLSID" содержит подключи с перекрестными ссылками на другие разделы. О подключах ProgID и VersionIndependentProgID мы уже говорили. Подключ TypeLib содержит GUID, который является идентификатором библиотеки типов для данного компонента; соответствующий подключ содержится в разделе "HKEY_CLASSES_ROOT\TypeLib". Подключ AppID содержит GUID, идентифицирующий подключ другого раздела - "HKEY_CLASSES_ROOT\AppID", и указывает на то, что объект является распределенным (DCOM). Подробнее об этих подключах будет сказано в соответствующих разделах.

В разделе CLSID имеется еще множество других подключей; мы не будем рассматривать их все. Упомянем лишь о тех, которые являются своего рода флагами, указывающими на тип компонента. Наличие подключа "Control" указывает, что компонент является элементом управления; "Insertable" - что объект может внедряться в контейнеры OLE; "OLEScript" - объект является исполнителем сценариев (scripting engine); "DocObject" - объект-документ; "Printable" - объект может быть распечатан; "Programmable" - объект является сервером автоматизации (и, соответственно, им можно управлять с помощью скриптов); "Ole1Class" - объект OLE 1.0 (старой версии).

Утилита для просмотра типов объектов

Создадим еще одну небольшую утилиту, которая будет отображать список зарегистрированных в реестре объектов, относящихся к одному из 7 перечисленных в конце предыдущего параграфа типов. Внешний вид приложения показан на рис. 4. Тип объекта можно выбрать в выпадающем списке; после нажатия кнопки "List" в окне списка отображаются описания соответствующих объектов (значения по умолчанию, связанные с тем или иным CLSID). Если описания нет, строка останется пустой.

Рис.4. Интерфейс утилиты COM_3
Рис.4. Интерфейс утилиты COM_3

Для экономии места мы не будем приводить здесь полный код приложения; соответствующие файлы находятся в каталоге COM_3 архива ComKit1.rar. Вместо этого рассмотрим лишь некоторые существенные моменты, имеющие отношение к логике работы программы.

Код (Text):
  1. invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey1,0,KEY_ALL_ACCESS,ADDR HKey
  2. .if eax==ERROR_SUCCESS
  3.  

Начинаем с того, что открываем раздел реестра "HKEY_CLASSES_ROOT\CLSID", сохраняя описатель в переменной HKey. При успешном открытии начинается основной цикл по перечислению имеющихся в открытом разделе ключей.

Код (Text):
  1. EnumLoop:
  2.     mov sbksz,255
  3.     invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
  4.     .if eax==ERROR_NO_MORE_ITEMS
  5.         jmp OutLoop
  6.     .elseif eax!=ERROR_SUCCESS
  7.         invoke MessageBox,0,ADDR Err2,ADDR App,MB_OK OR MB_ICONERROR
  8.         jmp OutLoop
  9.     .endif
  10.  

При любой ошибке перечисления цикл прерывается, причем если ошибка не связана с просмотром всех ключей, выводится соответствующее сообщение. Для каждого нового найденного ключа составляются две строки вида: "CLSID\{clsid компонента}" и "CLSID\{clsid компонента}\<тип объекта>", где <тип объекта> означает одну из 7 строк с типом объекта, выбранную из выпадающего списка.

Код (Text):
  1.     invoke lstrcpy,ADDR SubKey2,ADDR SubKey1
  2.     invoke lstrcpy,ADDR SubKey3,ADDR SubKey1
  3.     invoke lstrcat,ADDR SubKey2,ADDR s
  4.     invoke lstrcat,ADDR SubKey3,ADDR s
  5.     invoke lstrcat,ADDR SubKey2,ADDR SubKeyBuf
  6.     invoke lstrcat,ADDR SubKey3,ADDR SubKeyBuf
  7.     invoke lstrcat,ADDR SubKey3,ADDR s
  8.     invoke lstrcat,ADDR SubKey3,ADDR findbuf
  9.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey3,0,
  10. KEY_ALL_ACCESS,ADDR HKey2
  11. .if eax==ERROR_SUCCESS
  12.  

Затем делается попытка открыть ключ реестра "CLSID\{clsid компонента}\<тип объекта>". Успешная попытка означает, что ключ с данным CLSID имеет подключ с выбранным нами именем; в этом случае открываем другой подготовленный ключ ("CLSID\{clsid компонента}") и запрашиваем значение по умолчанию, которое пересылаем в окно списка и переходим к новой итерации:

Код (Text):
  1.     invoke RegCloseKey,HKey2
  2.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey2,0,
  3. KEY_ALL_ACCESS,ADDR HKey3
  4.     invoke RegQueryValueEx,HKey3,0,0,0,ADDR namebuf,ADDR bsz
  5.     invoke RegCloseKey,HKey3
  6.     invoke SendMessage,hLst,LB_ADDSTRING,0,ADDR namebuf
  7. .endif  ;RegOpenKeyEx (2)
  8.     inc idx
  9.     jmp EnumLoop
  10. OutLoop:
  11.     invoke RegCloseKey,HKey
  12.  

Раздел Interface

В отличие от остальных разделов, "HKEY_CLASSES_ROOT\Interface" относится не к отдельным компонентам, а к системе в целом. Приведенные здесь данные об интерфейсах используются при стандартном маршалинге. Подключей немного: BaseInterface, NumMethods, ProxyStubCLSID и ProxyStubCLSID32. BaseInterface содержит имя интерфейса, от которого унаследован данный интерфейс; если он не указан, в качестве базового принимается IUnknown. NumMethods содержит число методов в интерфейсе. ProxyStubCLSID(32) подобен InprocServer(32): он содержит полный путь к серверу (dll), реализующему вспомогательные объекты (т.н. представители и заглушки), которые используются системой при маршалинге методов данного интерфейса. Как обычно, суффикс "32" в имени означает 32-разрядный сервер, его отсутствие - 16-разрядный.

Подробное обсуждение маршалинга и связанных с ним проблем выходит за рамки данной статьи; кое-что будет рассказано во второй статье, а здесь приведем описание полезной утилиты, использующей данный раздел реестра. Внешний вид ее приведен на рис. 5. При нажатии кнопки "List" приложение создает экземпляр COM-объекта, CLSID которого указан в окне редактирования. Затем у созданного объекта запрашиваются все возможные интерфейсы, сведения о которых имеются в системном реестре. В окне списка выводятся имена всех интерфейсов, которые реализованы объектом.

Рис.5. Интерфейс утилиты COM_4
Рис.5. Интерфейс утилиты COM_4

Здесь опять приведем лишь ключевые моменты кода, исходные файлы содержатся в каталоге COM_4 архива ComKit1.rar.

Поскольку приложение использует библиотеку COM, при инициализации диалогового окна вызывается процедура CoInitialize, а при его закрытии - соответственно CoUninitialize. При нажатии кнопки "List" окно списка очищается, а из окна редактирования извлекается CLSID уже знакомым нам способом:

Код (Text):
  1.     invoke GetWindowText,hEd,ADDR buffer,255
  2.     invoke MultiByteToWideChar,CP_ACP,0,ADDR buffer,511,ADDR wbuf,255
  3.     invoke CLSIDFromString,ADDR wbuf,ADDR cls
  4.  

Затем создается экземпляр объекта с данным CLSID; в качестве начального интерфейса запрашивается IUnknown (его IID содержится в IUnk), а контекст объекта допускает любой тип сервера:

Код (Text):
  1.     invoke CoCreateInstance,ADDR cls,0, CLSCTX_SERVER,ADDR IUnk,ADDR pUnk

Если указатель успешно получен (в pUnk), открываем раздел реестра "HKEY_CLASSES_ROOT\Interface" и входим в главный цикл перечисления подключей (т.е. интерфейсов):

Код (Text):
  1.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey1,0,
  2. KEY_ALL_ACCESS,ADDR HKey
  3.     .if eax==ERROR_SUCCESS
  4. Enum1:
  5.     mov sbksz,255
  6.     invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
  7.     .if eax==ERROR_NO_MORE_ITEMS
  8.         jmp Ex_2
  9.     .elseif eax!=ERROR_SUCCESS
  10.         invoke MessageBox,0,ADDR Err2,ADDR App,MB_OK OR MB_ICONERROR
  11.         jmp Ex_2
  12.  

Выход из цикла осуществляется, когда функция перечисления возвращает ошибку; причем если это не связано с отсутствием ключей для дальнейшего перечисления, отображается сообщение об ошибке. Каждый новый ключ перечисления (IID интерфейса в строковом виде) преобразуется в числовой вид (если в ходе преобразования происходит ошибка, данный ключ просто игнорируется и цикл продолжается).

Код (Text):
  1.     .else
  2.         invoke MultiByteToWideChar,CP_ACP,0,ADDR SubKeyBuf,255,ADDR wbuf,255
  3.         invoke CLSIDFromString,ADDR wbuf,ADDR cls
  4.         .if eax!=NOERROR
  5.             jmp Cont1
  6.         .endif
  7.  

А вот теперь нужно запросить у нашего объекта интерфейс, IID которого мы только что получили. Для этого необходимо вызвать метод QueryInterface интерфейса IUnknown, со следующими аргументами:

  • указатель на экземпляр текущего объекта ("this");
  • адрес структуры с IID запрашиваемого интерфейса;
  • адрес переменной, в которой будет возвращен указатель затребованного интерфейса.

Адрес метода QueryInterface располагается в самом начале виртуальной таблицы (со смещением 0):

Код (Text):
  1.     lea eax,pItfc   ; pItfc получит указатель нового интерфейса
  2.     push eax
  3.     lea eax,cls     ; требуемый IID находится в cls
  4.     push eax
  5.     mov eax,pUnk    ; указатель на интерфейс IUnknown
  6.                 ; нашего объекта ("this")
  7.     push eax   
  8.     mov eax,[eax]   ; получаем адрес виртуальной таблицы
  9.     call dword ptr [eax]    ; вызываем первую функцию
  10. ; из виртуальной таблицы (QueryInterface)
  11.  

В случае успешного получения указателя нового интерфейса QueryInterface возвращает S_OK. Нам нужен просто факт поддержки объектом данного интерфейса, он сам не нужен; поэтому мы сразу же освобождаем только что полученный указатель, вызвав через него метод Release:

Код (Text):
  1. .if eax==S_OK
  2.     mov eax,pItfc   ; указатель на затребованный интерфейс
  3.     push eax        ; помещаем в качестве 1-го (и единственного) пар-ра;
  4.     mov eax,[eax]   ; адрес виртуальной таблицы
  5.     call dword ptr [eax+8]  ; вызываем 3-й метод из в.табл. (Release)
  6.  

Одновременно с этим создаем строку вида "Interface\{IID интерфейса}" и открываем соответствующий раздел реестра, запрашивая значение по умолчанию этого раздела (имя интерфейса). Это имя добавляется в окно списка.

Код (Text):
  1.     invoke lstrcpy,ADDR SubKey2,ADDR SubKey1
  2.     invoke lstrcat,ADDR SubKey2,ADDR s
  3.     invoke lstrcat,ADDR SubKey2,ADDR SubKeyBuf
  4.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey2,0,
  5. KEY_ALL_ACCESS,ADDR HKey2
  6.     .if eax==ERROR_SUCCESS
  7.         mov bsz,255
  8.         invoke RegQueryValueEx,HKey2,0,0,0,ADDR namebuf,ADDR bsz
  9.         .if eax!=ERROR_SUCCESS
  10.             invoke MessageBox,0,ADDR Err5,ADDR App,MB_OK OR MB_ICONERROR
  11.         .endif
  12.         invoke RegCloseKey,HKey2
  13.         invoke SendMessage,hLst,LB_ADDSTRING,0,ADDR namebuf
  14.     .endif  ;RegOpenKeyEx (2)
  15. .endif  ;S_OK (QueryIntervace)
  16.  

В случае, когда QueryInterface или RegOpenKeyEx возвращают код ошибки, данный ключ просто игнорируется, а цикл продолжается дальше.

Код (Text):
  1. Cont1:
  2.     inc idx
  3.     jmp Enum1
  4.     .endif  ;RegEnumKeyEx
  5. Ex_2:
  6.     invoke RegCloseKey,HKey
  7.  

После выхода из цикла метод Release вызывается также через указатель pUnk интерфейса IUnknown, полученный нами при создании экземпляра объекта (аналогично тому, как это было сделано ранее).

Раздел TypeLib

В разделе "HKEY_CLASSES_ROOT\TypeLib" содержатся ключи, имена которых представляют собой строковую форму идентификаторов (GUID) установленных в системе библиотек типов. На данные ключи ссылаются подключи TypeLib компонентов раздела "HKEY_CLASSES_ROOT\CLSID", но обратных ссылок нет. Библиотеки типов обязательны для компонентов, использующих позднее связывание и автоматизацию; в частности, они широко применяются с элементами управления ActiveX. Остальные компоненты могут и не иметь библиотеки типов.

Каждый ключ раздела TypeLib содержит иерархическую структуру подключей. На первом уровне находятся подключи версии библиотеки типов, представленные в строковой форме major.minor (цифры, соответствующие версии). В разделе версии находятся, в свою очередь, подключи HelpDir (полный путь к файлу справки), Flags (флаг библиотеки типов) и строковое представление локального идентификатора языка (lcid). Ключ <lcid> содержит еще один или два подключа: win16 и/или win32 - полные пути к библиотеке типов для соответствующей платформы.

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

Рис.6. Интерфейс утилиты COM_5
Рис.6. Интерфейс утилиты COM_5

Приложение работает следующим образом (детали реализации графического интерфейса опущены; полный исходный код содержится в каталоге COM_5 архива ComKit1.rar). При обработке сообщения WM_INITDIALOG открываем раздел реестра "HKEY_CLASSES_ROOT\CLSID", затем входим в главный цикл перечисления содержащихся в нем ключей:

Код (Text):
  1. invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey1,0,KEY_ALL_ACCESS,ADDR HKey
  2.     .if eax==ERROR_SUCCESS
  3. EnumLoop:
  4.         lea esi,buf
  5.         mov sbksz,255
  6.         invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
  7.         .if eax==ERROR_NO_MORE_ITEMS
  8.             jmp OutLoop
  9.         .elseif eax!=ERROR_SUCCESS
  10.             invoke MessageBox,0,ADDR Err2,ADDR App,MB_OK OR MB_ICONERROR
  11.             jmp OutLoop
  12.         .endif
  13.  

Как и в предыдущей утилите, если RegEnumKeyEx возвращает ошибку, цикл прерывается. Для каждого нового ключа составляются по две строки вида: "CLSID\{clsid компонента}" и "CLSID\{clsid компонента}\TypeLib", затем делается попытка открыть раздел реестра со второй строкой в качеcтве имени.

Код (Text):
  1.     invoke lstrcpy,ADDR SubKey2,ADDR SubKey1
  2.     invoke lstrcpy,ADDR SubKey3,ADDR SubKey1
  3.     invoke lstrcat,ADDR SubKey2,ADDR s
  4.     invoke lstrcat,ADDR SubKey3,ADDR s
  5.     invoke lstrcat,ADDR SubKey2,ADDR SubKeyBuf
  6.     invoke lstrcat,ADDR SubKey3,ADDR SubKeyBuf
  7.     invoke lstrcat,ADDR SubKey3,ADDR s
  8.     invoke lstrcat,ADDR SubKey3,ADDR findbuf
  9.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey3,0,
  10. KEY_ALL_ACCESS,ADDR HKey3
  11.  

Успешное открытие ключа TypeLib говорит о том, что для компонента имеется библиотека типов. В этом случае запрашиваем значение ключа "CLSID\{clsid компонента}" и помещаем соответствующую строку в первую колонку окна просмотра списков, а значение ключа "CLSID\{clsid компонента}\TypeLib" (строковое представление идентификатора библиотеки типов) - во вторую колонку:

Код (Text):
  1. .if eax==ERROR_SUCCESS
  2. invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,
  3. ADDR SubKey2,0,KEY_ALL_ACCESS,ADDR HKey2
  4.     .if eax==ERROR_SUCCESS
  5. mov bsz,255
  6. invoke RegQueryValueEx,HKey2,0,0,0,ADDR buf,ADDR bsz
  7.         mov eax,cnt
  8. mov item.iItem,eax
  9. mov item.iSubItem,0
  10. invoke SendMessage,hList,LVM_INSERTITEM,0,ADDR item
  11.     mov bsz,255
  12.     invoke RegQueryValueEx,HKey3,0,0,0,ADDR buf,ADDR bsz
  13.         .if eax==ERROR_SUCCESS
  14.             mov item.iSubItem,1
  15.             invoke SendMessage,hList,LVM_SETITEM,0,ADDR item
  16.         .endif
  17.         invoke RegCloseKey,HKey2
  18.         inc cnt
  19.     .endif  ;RegOpenKeyEx (SubKey2)
  20.     invoke RegCloseKey,HKey3
  21. .endif  ;RegOpenKeyEx (SubKey3)
  22. inc idx
  23. jmp EnumLoop
  24. OutLoop:
  25.     invoke RegCloseKey,HKey
  26.  

Двойной щелчок на элементе окна просмотра списка обрабатывается в сообщении WM_NOTIFY. Соответствующий код вынесен в отдельную процедуру FindHelp, причем в регистре ebx передается адрес структуры NMLISTVIEW с информацией об элементе списка, на котором был произведен двойной щелчок:

Код (Text):
  1. .ELSEIF uMsg==WM_NOTIFY
  2.     mov ebx,lParam
  3.     assume ebx:ptr NMHDR
  4.     .if [ebx].code==NM_DBLCLK
  5.         call FindHelp
  6.     .endif
  7.     assume ebx:nothing
  8.  

Процедура FindHelp начинается с проверки того, что щелчок действительно произведен на элементе списка:

Код (Text):
  1. FindHelp proc
  2.     assume ebx:ptr NMLISTVIEW
  3.     mov eax,[ebx].iItem
  4.     mov item.iItem,eax
  5.     .if eax==-1
  6.         ret
  7.     .endif
  8.     assume ebx:nothing
  9.  

Теперь можно скопировать в буфер buf содержимое второй колонки соответствующего элемента (GUID библиотеки типов в текстовом виде).

Код (Text):
  1.     mov item.imask,LVIF_TEXT
  2.     mov item.iSubItem,1
  3.     mov item.pszText,OFFSET buf
  4.     mov item.cchTextMax,255
  5.     invoke SendMessage,hList,LVM_GETITEM,0,ADDR item
  6.  

Из полученного значения составляем название раздела реестра "TypeLib\{GUID библиотеки}" и открываем его.

Код (Text):
  1.     invoke lstrcpy,ADDR SubKey2,ADDR cl2
  2.     invoke lstrcat,ADDR SubKey2,ADDR s
  3.     invoke lstrcat,ADDR SubKey2,ADDR buf
  4.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey2,
  5. 0,KEY_ALL_ACCESS,ADDR HKey
  6.  

Нужно обработать не очень удобную форму представления данных для библиотеки типов. Чтобы получить путь к файлу библиотеки типов, необходимо сначала указать версию, а о ней заранее невозможно сказать ничего определенного. Плохо то, что функция для загрузки LoadRegTypeLib требует точного указания старшей (major) версии и отказывается загружать библиотеки с другими версиями. Поэтому придется "вручную" просмотреть подключи с версиями и выделить из имени подключа старшую версию.

Код (Text):
  1.     .if eax==ERROR_SUCCESS
  2.         mov idx,0
  3.         mov mjver,0 ; для нахождения max значения majversion
  4. TlibLoop:
  5.         mov sbksz,255
  6.         ; перечислим подключи в разделе 'HKCR\TypeLib\{GUID}'
  7.         ; (в форме majversion.minversion)
  8.         invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
  9.         .if eax==ERROR_SUCCESS
  10.             xor eax,eax
  11.             mov al,SubKeyBuf    ; первый символ подключа (т.е. majversion)
  12.             sub eax,30h     ; символ преобразуем в значение
  13.             cmp mjver,eax
  14.             jge Tlib1       ; выбираем для версии большее значение
  15.             mov mjver,eax
  16. Tlib1:
  17.             inc idx
  18.             jmp TlibLoop
  19.         .endif  ;RegEnumKeyEx
  20.         invoke RegCloseKey,HKey
  21.  

Мы получили номер старшей версии; теперь можно обычным способом преобразовать строковое представление GUID в числовое и передать всё функции LoadRegTypeLib, которая принимает следующие аргументы:

  • адрес структуры с GUID загружаемой библиотеки;
  • старшая версия загружаемой библиотеки (необходимо указать точно; в нашем случае она находится в переменной mjver);
  • младшая версия загружаемой библиотеки (здесь можно оставить 0, потому что система загрузит любую младшую версию, превышающую указанную);
  • локальный языковой идентификатор (lcid; можно оставить нейтральный - 0);
  • адрес переменной, в которой будет возвращен указатель на интерфейс ITypeLib.

Код (Text):
  1.     invoke MultiByteToWideChar,CP_ACP,0,ADDR buf,-1,ADDR wbuf,510
  2.     invoke CLSIDFromString,ADDR wbuf,ADDR TlibGuid
  3.     .if eax==NOERROR
  4.         invoke LoadRegTypeLib,ADDR TlibGuid,mjver,0,0,ADDR pTlib
  5.         .if eax==S_OK  
  6.  

Возвращенное значение S_OK говорит о том, что у нас есть действительный указатель на интерфейс ITypeLib. Нас интересует метод GetDocumentation, имеющий следующие аргументы:

  • индекс описания типа; если -1, возвращаются данные для самой библиотеки типов (что нам и нужно);
  • адрес переменной, в которой будет возвращен указатель на строку типа BSTR с именем запрашиваемого элемента (в нашем случае – самой библиотеки типов);
  • адрес переменной, в которой будет возвращен указатель на строку типа BSTR с описанием запрашиваемого элемента;
  • адрес переменной, в которой будет возвращен контекстный идентификатор для файла справки;
  • адрес переменной, в которой будет возвращен указатель на строку типа BSTR с полным путем к файлу справки.

Если какой-нибудь вид данных не нужен, можно передать в соответствующем аргументе 0. Адрес функции GetDocumentation расположен в виртуальной таблице интерфейса ITypeLib по смещению 24h.

Код (Text):
  1.     push OFFSET HelpFile
  2.     push 0      ; контекст не нужен, pBstrHelpCtx=NULL
  3.     push OFFSET DocString
  4.     push OFFSET CompName
  5.     push -1     ; индекс (-1='сама библиотека')
  6.     mov eax,pTlib
  7.     push eax        ; указатель ‘this’
  8.     mov eax,[eax]   ; виртуальная таблица
  9.     call dword ptr [eax+24h]    ;GetDocumentation</pre></code>
  10. <p>Если по возвращении из метода в переменной HelpFile окажется 0, это значит, что файла справки нет. В этом случае выведем простое окно сообщения с полученными сведениями:
  11. <p><pre><code>.if HelpFile==0  
  12.     invoke WideCharToMultiByte,CP_ACP,0,CompName,-1,ADDR SubKeyBuf,255,0,0
  13.     invoke lstrcpy,ADDR wbuf,ADDR SubKeyBuf
  14.     invoke lstrcat,ADDR wbuf,ADDR CRLF
  15.     invoke WideCharToMultiByte,CP_ACP,0,DocString,-1,ADDR SubKeyBuf,255,0,0
  16.     invoke lstrcat,ADDR wbuf,ADDR SubKeyBuf
  17.     invoke lstrcat,ADDR wbuf,ADDR CRLF
  18.     invoke lstrcat,ADDR wbuf,ADDR ms1
  19.     invoke MessageBox,0,ADDR wbuf,ADDR App,MB_OK or MB_ICONINFORMATION</pre></code>
  20. <p>Если же файл справки указан, преобразуем соответствующую строку из Unicode в ANSI и передадим ее адрес в качестве аргумента функции ShellExecute (чтобы иметь возможность загружать как hlp-, так и chm-файлы):
  21. <p><pre><code>.else         ; загрузить и отобразить файл справки
  22.     invoke WideCharToMultiByte,CP_ACP,0,HelpFile,-1,ADDR buf,255,0,0
  23.     invoke ShellExecute,0,ADDR cmd,ADDR buf,0,0,SW_SHOW
  24.     .if eax<33  ; ошибка ShellExecute
  25.         invoke wsprintf,ADDR wbuf,ADDR fmt,ADDR buf
  26.         invoke MessageBox,0,ADDR wbuf,ADDR App,MB_OK or MB_ICONERROR
  27.     .endif
  28. .endif      ;HelpFile==0</pre></code>
  29. <p>Строки типа BSTR необходимо освободить, во избежание утечки ресурсов:
  30. <p><pre><code>  invoke SysFreeString,HelpFile  
  31.     invoke SysFreeString,CompName
  32.     invoke SysFreeString,DocString</pre></code>
  33. <p>При ошибке вызова метода GetDocumentation отображаем соответствующее сообщение. В любом случае после этого необходимо освободить указатель интерфейса ITypeLib (pTlib):
  34. <p><pre><code>  .else               ; ошибка вызова GetDocumentation
  35.         invoke MessageBox,0,ADDr Err6,ADDR App,MB_OK or MB_ICONERROR
  36.     .endif          ; GetDocumentation
  37.     mov eax,pTlib
  38.     push eax            ; указатель ‘this’
  39.     mov eax,[eax]       ; виртуальная таблица
  40.     call dword ptr [eax+8]  ; функция №3 (Release)

Завершают функцию обработки других ошибок:

Код (Text):
  1.         .else       ; ошибка LoadRegTypeLib
  2.             invoke MessageBox,0,ADDR Err5,ADDR App,MB_OK or MB_ICONERROR
  3.         .endif  ; LoadRegTypeLib==S_OK
  4.     .else           ; ошибка CLSIDFromString
  5.         invoke MessageBox,0,ADDR Err4,ADDR App,MB_OK or MB_ICONERROR
  6.     .endif      ; CLSIDFromString==NOERROR
  7. .else               ; ошибка RegOpenKeyEx
  8.     invoke MessageBox,0,ADDR Err7,ADDR App,MB_OK or MB_ICONERROR
  9.     ret
  10. .endif          ; RegOpenKeyEx==ERROR_SUCCESS
  11. ret
  12. FindHelp endp

С помощью данной утилиты можно обнаружить множество интересных вещей. Попробуйте!

Раздел AppID

Раздел реестра «HKEY_CLASSES_ROOT\AppID» совместно с разделом «HKEY_LOCAL_MACHINE\Software\Microsoft\OLE» определяют установки для распределённой системы COM (DCOM).

Распределённая система имеет дело с передачей данных по сети, и перед ней сразу же встают вопросы аутентификации, авторизации и другие проблемы обеспечения безопасности. Общая схема конфигурирования обычно такова: в разделе «HKEY_LOCAL_MACHINE\Software\Microsoft\OLE» описываются значения параметров безопасности по умолчанию. Раздел «HKEY_CLASSES_ROOT\AppID» содержит ключи (GUID), в которых параметры безопасности могут задаваться для отдельных групп компонентов (в этом случае «HKEY_CLASSES_ROOT\CLSID\{clsid компонента}\AppID» содержит ссылку на соответствующий ключ раздела «HKEY_CLASSES_ROOT\AppID»). Значения из раздела AppID имеют преимущество перед соответствующими значениями по умолчанию. Кроме того, параметры безопасности и другие связанные с распределенной системой параметры могут задаваться явным образом при создании компонента с использованием функции CoCreateInstanceEx или ей подобной. В этом случае используются явно заданные значения.

Раздел «HKEY_LOCAL_MACHINE\Software\Microsoft\OLE» может иметь следующие именованные значения:

  • EnableDCOM – разрешает (Y или y) или запрещает (N или n) удаленным клиентам запуск компонентов на данной системе;
  • DefaultLaunchPermission – определяет список участников (ACL), кто может запускать по умолчанию компоненты на данной машине;
  • DefaultAccessPermission – определяет список участников (ACL), кто может по умолчанию получать доступ к запущенным объектам;
  • LegacyAuthenticationLevel – устанавливает уровень аутентификации по умолчанию;
  • LegacyImpersonationLevel – устанавливает уровень имперсонации по умолчанию;
  • LegacyMutualAuthentication – разрешает (Y или y) или запрещает (любое другое значение или отсутствие именованного значения) взаимную аутентификацию;
  • LegacySecureReferences – указывает, охраняются (Y или y) или нет (любое другое значение или отсутствие именованного значения) вызовы AddRef и Release.

Ключи «HKEY_CLASSES_ROOT\AppID\{GUID}» могут иметь следующие именованные значения:

  • RemoteServerName – имя сервера, на котором должен быть запущен компонент;
  • ActivateAtStorage – указывает, что компонент должен быть запущен на той же машине, на которой хранятся данные объекта;
  • LocalService – указывает, что компонент реализован в виде Win32-сервиса;
  • ServiceParameters – используется совместно с LocalService. Значение этого параметра передается при запуске соответствующего сервиса в виде аргументов командной строки;
  • RunAs – позволяет запустить компонент, не являющийся сервисом, от имени определенного пользователя;
  • LaunchPermission – определяет список участников (ACL), кто может запустить сервер с данным компонентом;
  • AccessPermission – определяет список участников (ACL), кто может получить доступ к объектам данного класса;
  • DllSurrogate – содержит полный путь к программе-оболочке (exe), в которой должен быть запущен удаленный внутрипроцессный (dll) сервер;
  • AuthenticationLevel – определяет уровень аутентификации для компонента.

Указанные в реестре значения могут быть перекрыты явным вызовом функции CoInitializeSecurity. © Roustem


0 2.566
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532