Приглашаем на семинар по эффективной разработке информационных систем с помощью IBM Rational
 (495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

.NET в unmanaged окружении - использование и родовые проблемы (исходники)

Источник: habrahabr

Managed код и .NET Framework - совершенно замечательная вещь с точки зрения программиста, которому надо кровь из носу выдавать максимально стабильно работающие программы. Использование .NET позволяет очень сильно сократить затраты на разработку, тестирование и сопровождение программных продуктов, особенно по сравнению с C++ или Delphi.

Однако, managed код имеет одну очень серьезную родовую травму, которая прямо проистекает из его достоинств - он изначально несовместим с unmanaged средой, в которой вынужден работать. Boxing, поля памяти, отсутствие прямой адресации и прочие ухищрения, призванные облегчить жизнь программисту, приводят к тому, что взаимодействие managed и unmanaged кода становится проблемой.

Однако нет такой проблемы, которую нельзя решить (пусть даже с помощью топора и лома). Сегодня у нас краткий обзор возможностей организации взаимодействия между managed и unmanaged кодом. Многие C# и особенно VB.NET программисты боятся этого, но на самом деле в этом нет ничего страшного. Начнем мы с самых примитивных методов, которые будут интересны разве что новичкам (поэтому матерые волки .NET могут с чистой совестью первую часть статьи пропустить), и закончим описанием того, что делать, если хочется написать программу на .NET, но сделать это невозможно (а такое тоже бывает). Естественно, к каждому случаю будут приведены конкретные примеры, быть может, хабрачеловеки расскажут мне о моей собственной велосипедности. Параллельно я скажу пару слов о подводных камнях при работе с VSTO и Windows Shell.

Работа с unmanaged-кодом из managed.

Стандартными для такой ситуации с точки зрения .NET являются три механизма, предлагаемых Microsoft - это procedure invoke (P/Invoke), COM interop и unsafe.

Общие предостережения

У всех методов работы с unmanaged-кодом есть набор общих недостатков, достаточно серьезных, на которые стоит обратить внимание, и с которыми стоит считаться.

  1. Вызов unmanaged-кода - это опасная операция, которая требует определения допустимости данной операции в манифесте приложения. Это иногда создает проблемы при деплоинге, особенно методами ClickOnce.
  2. Unmanaged-код не является защищенным, не проходит проверок CLR и не следует правилам встроенной системе безопасности .NET. Если вы определили в методе правило безопасности "запретить доступ к файлам" и используете unmanaged-функцию, которая с файлами работает - она отработает без проблем.

Держа в уме данную информацию, перейдем к рассмотрению методов работы с ним.

P/Invoke

Механизм procedure invoke предназначен для обращения к функциям, лежащих в DLL-файлах.

Когда это нужно?

Несмотря на обилие функций, лежащих в библиотеках .NET Framework, некоторые вещи сделать при помощи него все равно принципиально невозможно. Лично мне возможность P/Invoke потребовалась, чтобы управлять mandatory level для файлов и named pipes. Проблема состояла в том, что классы контроля доступа .NET не понимает SDDL-строк, которые содержат описатели mandatory level, и пришлось обращаться к этим функциям напрямую.

Как это выглядит?

Достаточно просто. Объявляем статический класс, в нем прописываем статическую функцию с именем той, которую надо экспортировать, составляем соответствующий список параметров, вешаем атрибут DllImport с именем библиотеки DLL и радуемся жизни.

Пример: импортируем функцию PlaySound

public static class Win32
{
[DllImport("winmm")]
public static extern int PlaySound(string szSound, IntPtr hModule, int flags);
}

* This source code was highlighted with Source Code Highlighter.

Основные проблемы

  1. Передача всевозможных описателей (handles). Проблема решается следующим образом. У большинства объектов существует поле Handle, которое можно передавать в функцию. Если его нет - можно попробовать взять SafeHandle объекта и запросить у него IntPtr через Dangerous-операцию.
  2. Передача структур, указателей на функции и прочих нетривиальных вещей. Действительно, для неспециалиста, который не понимает, как маршаллить данные, это очень большая проблема. К счастью, существует сайт pinvoke.net/, который содержит в себе основные определения для часто используемых функций. Сайт построен на вики-движке и если вам понадобилась системная функция - на 99%, что вы там найдете ее правильное определение.

COM Interop

Действительно мощный механизм, который позволяет использовать в managed-коде COM-компоненты.

Когда это нужно?

В серьезных проектах - достаточно часто. Дело в том, что определенные части системы могут писаться (или уже быть написаны) на С++, Delphi или даже (свят-свят) Visual Basic (тот что не .NET). Кроме того, Win32 сама построена на COM, а значит, предоставляет доступ к функционалу в том числе и через этот механизм. Для того, чтобы пользоваться им без проблем - следует воспользоваться компонентной моделью и COM Interop.

Как это выглядит?

Достаточно просто. Если ваш объект поддерживает automation - то процесс интеропа пройдет без проблем, если нет - возможно придется помучиться.

Чтобы добавить COM-объект в сборку, просто подключите его через References проекта. Библиотека, которая маршалит параметры из unmanaged в managed и обратно, создастся автоматически. Естественно, для сложных объектов, например MAPI или AD, такой интероп работать не будет, в таком случае, следует воспользоваться подходом, о котором я напишу далее.

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

IComExampleInterface iInt = new ComExampleInterface();

* This source code was highlighted with Source Code Highlighter.

Нет, мы не создаем объект интерфейса. На самом деле, у этого интерфейса (это видно в метаданных) есть атрибут под названием default coclass, и именно этот объект и создастся.
Как видите, это очень просто и совсем не страшно.

Основные проблемы

  1. Ошибки при интеропе сложных объектов. .NET не всемогущ, и не всегда может правильно определить, как маршаллятся те или иные данные. В таком случае, опять попадаем в ситуацию, когда следует прописывать маршаллинг явно.
  2. Отсутствие в .NET многих определений стандартных интерфейсов. Например, IUnknown, IDispatch, IDictionary или IStream. Эти определения можно написать руками или выдрать с помощью Reflector из недр ядра .NET (они там есть).
  3. Несовместимость одноименных объектов .NET и Win32. Например, Dictionary в .NET это совсем не то же самое, что объект scripting.Dictionary Win32. Аналогично с IStream и прочими, поэтому для успешной и плодотворной работы придется писать обертки и экспортировать их. Как? Например, создавая обертки и экспортируя их методами ComExport, о котором далее.

Unsafe-контекст.

Лично мне кажется ненужным атавизмом, но это лично мое мнение.

Когда это нужно?

Чаще всего, применение unsafe-методов мотивируют тем, что подобный подход уменьшает время выполнения кода, особенно в случае работы с большими объемами данных простых типов - в этом случае затраты на boxing/unboxing становятся достаточно велики. Однако, как показывает практика, в реальности программисты, работающие с .NET, все больше оперируют сложными объектами вроде DataSet, кроме того, unsafe требует определения в манифесте приложения специальных полномочий на выполнение небезопасного кода. Лично у меня необходимости применять unsafe не было никогда - даже задачи маршаллинга сложных типов данных с легкостью выполняются с помощью статического класса Marshal с использованием IntPtr.

Как это выглядит?

К описанию метода добавляется ключевое слово unsafe.

public static unsafe void UnsafeMethod(char* chararray)

* This source code was highlighted with Source Code Highlighter.

При этом в методе становятся доступны операции взятия адреса и механизм работы со ссылками, так же, как в С++.
Следует помнить, что unsafe-код остается при этом managed, просто снижается число проверок времени выполнения. Таким образом, он не совсем относится к нашей теме, но упомянуть об этом способе работы лишним не будет.

Основные проблемы

  1. Появление многих наследованных проблем unmanaged-кода, включая главную - переполнение буфера.

Работа с managed-кодом из unmanaged.

Если работа с unmanaged-кодом организована более-менее хорошо, то обратный процесс - обращение к .NET сборкам из unmanaged-кода создает огромное количество проблем. Чтобы понять их суть, рассмотрим механизм, который предлагается Microsoft для релизации подобного подхода.

Механизм этот называется ComExport и позволяет экспортировать объекты managed-среды так, как будто это обычные COM-объекты. Для экспорта объекта, следует определить для него атрибут ComVisible(true), задать CLSID, ProgID, DefaultInterface, провести аналогичную операцию для всех экспортируемых интерфейсов. Особенно важным является определение CLSID и IID для всех экспортируемых интерфейсов, а также их версий, жестко в коде - иначе при разработке приложения любое изменение интерфейса будет приводить к определению нового CLSID и IID, а версия будет постоянно скакать.

  [ComVisible(true)]
  [InterfaceType(ComInterfaceType.InterfaceIsDual)]
  [Guid("5095474B-5273-44ae-A220-9E3820D2EEDC")]
  [ProgId("COM.Export.Test.1")]
  [ComDefaultInterface(typeof(IDefaultInterface))]
  public class Coclass {...}

* This source code was highlighted with Source Code Highlighter.

Однако если бы все было так радужно - никаких проблем бы не было. Реальность гораздо более грустная и существует ряд проблем, которые могут затруднить использование такого подхода.

  1. COM, написанные с использованием .NET требуется регистрировать не с помощью regsvr32, а с помощью regasm, что несколько затрудняет разработку инсталляторов.
  2. Созданные компоненты требуется подписывать (хотя бы временным ключом), либо регистрировать в GAC с помощью gacutil. Регистрация в GAC требует администраторских полномочий в Windows Vista.
  3. С помощью механизма ComExport невозможно создать Out-Process COM.
  4. При использовании таких компонентов, CLR загружается в адресное пространство процесса, который его вызывает.

Остановимся на последних двух пунктах несколько подробнее. Почему же нельзя создать out-process COM?

Если говорить совсем просто, то дело в том, что любое приложение .NET является еще и сборкой, и позволяет подключать себя в качестве простой assembly DLL, без выполнения того когда, который непосредственно прописан в функции main. И если при написании out-process COM, например, на C++, характерным поведением системы является дождаться загрузки приложения, затем подождать, пока она зарегистрирует экспортируемые объекты в ROT, после чего обратиться к нему с запросом фабрики класса, то в случае с .NET приложение не запускается - сборка просто стыкуется к текущему процессу в качестве DLL через посредника. Таким образом, даже если вы в сборке определите синглтон или static-класс - он будет свой для каждого процесса, так как процессы в win32 изолированы друг от друга.

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

А в чем же проблема загрузки CLR в адресное пространство процесса, спросите вы? Ведь .NET поддерживает версионность сборок, и в любом случае будет использована та версия mscorlib, которая соответствует библиотеке.

Не все так просто. Проблемы начнутся тогда, когда вы попытаетесь загрузить COM, написанный с помощью .NET Framework 1.1 в приложении, написанном на .NET Framework 3.5. Две верии одной библиотеки не могут одновременно существовать в одном адресном процессе. Скорее всего, все просто упадет.

Именно этот момент, в частности, прямо запрещает написание таких вещей, как shell extension или namespace extension с помощью .NET. Ведь диалоговое окно сохранения файла или открытия может быть вызвано из любого кода - unmanaged или managed, написанного с помощью любой версии Framework.

Кроме того, положа руку на сердце, ComExport - не самый быстрый механизм. Загрузка CLR - достаточно долгий процесс. Особенно это очевидно в случае разработки add-in к приложениям Microsoft Office с помощью Visual Studio Tools for Office. Средство, планировавшееся как панацея от всех проблем разработки, увязло в одной простой проблеме - с подключенным VSTO add-in время загрузки приложения увеличивается в среднем на величину от 20 до 100 секунд. И если для MS Outlook это не слишком критично, то за увеличение времени загрузки Word или Excel клиенты еще долго будут гоняться за вами со ссаными тряпками :)

Просто запомните - реализовывать shell extensions с помощью .NET нельзя. Не слушайте Microsoft, который в MSDN мягко "не рекомендует" - нельзя. Никогда. Точка.

Для этой проблемы существуют два решения:

  1. Использование proxy-объеков, не выпускающих CLR свои пределы.
  2. Использование custom транспортов для связи между managed и unmanaged кодом.

Когда у меня появилась задача написания namespace extension, которое бы обращалось к серверу, написанному на ASP.NET, который экспортировал API через механизм WCF, проблема для меня встала действительно остро. Налаживать соединения через сокеты, реализовывать механихм аутентификации Windows и парсить SOAP желания не было, а Managed C++ использовать запрещали описанные выше проблемы. Тогда родилась следующая архитектура: namespace extension, написанный на C++, который реализует все интерфейсы Windows Shell, а за данными обращается к приложению, запущенному на локальной (или удаленной) машине, используя канал named pipe. Из этого подхода родилась общая архитектура подобного рода приложений, названная .NET Pipe RPC. Подход действительно достаточно общий, однако у меня в Ворде величина этой статьи составляет уже 5 страниц, поэтому я заканчваю, и если хабрачеловеки заинтересовались этой темой - отпишите в комментариях, и я подробно расскажу об этом подходе с примерами реализации.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 24.04.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Enterprise Connectors (1 Year term)
ABBYY Lingvo x6 Английская Профессиональная версия
ARCHICAD 21, локальная лицензия на 12 месяцев
Symantec Endpoint Protection Small Business Edition, Initial Hybrid Subscription License with Support, 1-24 Devices 1 YR
NERO 2016 Classic ESD. Электронный ключ
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Программирование на Microsoft Access
CASE-технологии
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Реестр Windows. Секреты работы на компьютере
СУБД Oracle "с нуля"
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 

Приглашаем на семинар по эффективной разработке информационных систем с помощью IBM Rational


    
rambler's top100 Rambler's Top100 Рейтинг@Mail.ru