Построение COM-компонентов для взаимодействия

Если планируется создание приложений на основе модели COM, можно разработать собственный код для обеспечения эффективного взаимодействия с управляемым кодом. Используя перспективное планирование, можно также упрощать переход с неуправляемого кода на управляемый код.

Приводимые ниже рекомендации обобщают наиболее удачные примеры написания COM-типов, обеспечивающих взаимодействие с управляемым кодом.

Предоставляйте библиотеки типов

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

Библиотеку типов можно упаковать в отдельный файл или встроить в качестве ресурса в DLL-, EXE- или OCX-файл. Более того, метаданные можно создавать напрямую, что позволяет подписывать метаданные парой ключей издателя. Метаданные, подписанные ключом, имеют определенный источник и могут помочь предотвратить связывание при использовании вызывающим объектом неправильного ключа, тем самым повышая безопасность.

Регистрируйте библиотеки типов

Чтобы правильно маршалировать вызовы, среде выполнения требуется найти библиотеку типов, которая описывает конкретный тип. За исключением случая позднего связывания библиотека типов должна быть зарегистрирована, чтобы стать видимой для среды выполнения.

Библиотеку типов можно зарегистрировать путем вызова функции, LoadTypeLibEx Microsoft Win32 API с флагом regkind, для которого установлено значение REGKIND_REGISTER. Программа Regsvr32.exe автоматически регистрирует библиотеку типов, встроенную в DLL-файл.

Используйте безопасные массивы вместо массивов переменной длины

Безопасные массивы COM описывают себя сами. Проверяя безопасные массивы, упаковщик среды выполнения может определить ранг, размер, границы и обычно тип содержимого массива во время выполнения. Массивы переменной длины (или в стиле языка C) не обладают таким же свойством самоописания. Например, сигнатура следующего неуправляемого метода не предоставляет сведений о параметрах массива кроме типа элемента.

HRESULT DoSomething(int cb, [in] byte buf[]);

В действительности массив не отличается от любого другого параметра, передаваемого по ссылке. В результате программа Tlbimp.exe не преобразует параметр массива для метод DoSomething. Вместо этого, как показывает следующий пример кода, массив появляется в виде ссылки на тип Byte.

Public Sub DoSomething(cb As Integer, ByRef buf As Byte)
public void DoSomething(int cb, ref Byte buf);

Чтобы улучшить взаимодействие, в сигнатуре неуправляемого метода можно ввести в качестве аргумента SAFEARRAY. Пример.

HRESULT DoSomething(SAFEARRAY(byte)buf);

Программа Tlbimp.exe преобразует SAFEARRAY в следующий тип управляемого массива:

Public Sub DoSomething(buf As Byte())
public void DoSomething(Byte[] buf);

Используйте типы данных, совместимые с Automation

Служба маршалинга среды выполнения автоматически поддерживает все типы данных, совместимые с Automation. Типы, не совместимые с Automation, могут поддерживаться или не поддерживаться.

Предоставляйте в библиотеках типов сведения о версии и языковом стандарте

При импорте библиотеки типов в сборку также передаются сведения о версии и языковом стандарте библиотеки типов. Управляемые клиенты затем могут быть связаны с конкретной версией или языковым стандартом сборки или с самой последней версией сборки. Предоставление сведений о версии в библиотеке типов позволяет клиентам точно выбрать версию используемой сборки.

Используйте непреобразуемые типы

При упаковке и передаче типы данных могут быть непреобразуемыми и преобразуемыми. Непреобразуемые типы имеют общее представление на границе взаимодействия. Целочисленный тип и тип чисел с плавающей запятой являются преобразуемыми типами. Массивы и структуры преобразуемых типов также являются непреобразуемыми. Строки, даты и объекты представляют собой примеры преобразуемых типов, которые преобразуются во время процесса маршалинга.

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

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

Реализуйте IProvideClassInfo

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

Например, рассмотрим сигнатуру следующего COM-метода:

interface INeedSomethng {
   HRESULT DoSomething(IBiz *pibiz);
}

При импорте метод преобразуется следующим образом:

Interface INeedSomething
   Sub DoSomething(pibiz As IBiz)
End Interface
interface INeedSomething {
   void DoSomething(IBiz pibiz);
}

Если в интерфейс IBiz передается управляемый объект, реализующий интерфейс INeedSomething, то упаковщик взаимодействия, при первоначальном введении IBiz в управляемый код, пытается упаковать интерфейс с помощью оболочки объекта, соответствующей конкретному типу. Чтобы определить правильный тип оболочки, упаковщику должен быть известен тип объекта, реализующего интерфейс. Один из способов определения упаковщиком типа объекта состоит в запросе интерфейса IProvideClassInfo. Если объект реализует интерфейс IProvideClassInfo, упаковщик определяет тип объекта и упаковывает интерфейс в оболочку для конкретного типа.

Используйте модульные вызовы

Маршалинг данных между управляемым и неуправляемым кодом связан с дополнительными расходами ресурсов. Расход ресурсов можно уменьшить, сокращая число переходов через границу. Интерфейсы, которые минимизируют число переходов, обычно работают лучше, чем интерфейсы с частыми переходами границы, выполняющими ряд небольших задач при каждом переходе.

Используйте с осторожностью значения HRESULT при сбоях

Когда управляемый клиент вызывает COM-объект, в среде выполнения происходит отображение значений ошибки HRESULT COM-объектов в исключения, которые упаковщик вызывает при возврате из вызова. Управляемая модель исключений оптимизирована для случаев, которые не являются исключительными; при отсутствии исключений дополнительные затраты на их перехват практически не возникают. Наоборот, когда исключение действительно возникает, его перехват может потребовать значительных дополнительных издержек.

Используйте исключения экономно и избегайте возвращения сбойных значений HRESULT в справочных целях. Резервируйте значения HRESULT при сбоях для исключительных ситуаций. Учитывайте, что чрезмерное использование значений HRESULT при сбоях может негативно влиять на производительность.

Явно освобождайте внешние ресурсы

Некоторые объекты на протяжении своего существования используют внешние ресурсы; например, подключение к базе данных может обновлять набор записей. Обычно объект удерживает внешний ресурс на протяжении всего своего существования, тогда как явное освобождение может вернуть ресурс немедленно. Например, можно использовать метод Close объекта файла вместо закрытия файла в деструкторе класса или применения IUnknown.Release. Предоставляя эквивалент метода Close в своем коде, разработчик может освободить внешний файловый ресурс, хотя существование объекта продолжится.

Избегайте переопределения неуправляемых типов

Правильный способ реализации существующего COM-интерфейса в управляемом коде заключается в первоначальном импорте определения интерфейса с помощью программы Tlbimp.exe или эквивалентной функции интерфейса API. Получающиеся метаданные предоставляют совместимое определение COM-интерфейса (идентичный IID, идентичные DispId и т. д.).

Избегайте ручного переопределения COM-интерфейсов в управляемом коде. Такая операция отнимает время и редко обеспечивает получение управляемого интерфейса, совместимого с существующим COM-интерфейсом. Вместо работы вручную для сохранения совместимости определений используйте программу Tlbimp.exe.

Избегайте использовать значения HRESULT успешных вызовов

Перехват исключений является самым естественным способом обработки ошибок управляемыми приложениями. Чтобы сделать использование COM-типов прозрачным, всякий раз, когда COM-метод возвращает значение HRESULT при сбоях, в среде выполнения автоматически создаются исключения.

Если COM-объект возвращает значение HRESULT успешного вызова, среда выполнения возвращает значение параметра retval. По умолчанию значение HRESULT сбрасывается, что сильно затрудняет для управляемого клиента проверку значения HRESULT успешного вызова. Хотя можно сохранить значение HRESULT с помощью атрибута PreserveSigAttribute, этот процесс требует определенных усилий. Необходимо вручную добавить этот атрибут в сборку, созданную с помощью программы Tlbimp.exe или эквивалентной функции API.

По возможности лучше избегать использования значений HRESULT успешных вызовов. Вместо этого можно возвращать сведения о состоянии вызова с помощью параметра Out.

Избегайте использовать модульные функции

Библиотеки типов могут содержать функции, определенные для модуля. Обычно эти функции используются для предоставления сведений о типе для точек входа DLL. Программа Tlbimp.exe не импортирует эти функции.

Избегайте использовать члены System.Object в интерфейсах по умолчанию

Управляемые клиенты и компонентные COM-классы взаимодействуют с помощью программ-оболочек, предоставляемых средой выполнения. При импорте COM-типа процесс преобразования добавляет все методы интерфейса компонентного класса по умолчанию в класс оболочки, производный от класса System.Object. Будьте внимательны при именовании членов интерфейса по умолчанию, чтобы избежать конфликтов именования с членами System.Object. В случае конфликта импортированные методы переопределяют методы базового класса.

Данная операция может быть предпочтительна, если метод интерфейса по умолчанию и метод System.Object предоставляют одинаковые возможности. Но эта операция может быть источником проблем, если методы интерфейса по умолчанию используются непреднамеренно. Чтобы предотвратить конфликты имен, избегайте использовать в интерфейсах по умолчанию следующие имена: Object, Equals, Finalize, GetHashCode, GetType, MemberwiseClone и ToString.

См. также

Ссылки

Tlbimp.exe (программа экспорта библиотек типов)

Основные понятия

Вопросы разработки для взаимодействия