|
|
|||||||||||||||||||||||||||||
|
.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-кодом есть набор общих недостатков, достаточно серьезных, на которые стоит обратить внимание, и с которыми стоит считаться.
Держа в уме данную информацию, перейдем к рассмотрению методов работы с ним.
p/invokeМеханизм procedure invoke предназначен для обращения к функциям, лежащих в dll-файлах.
Когда это нужно?Несмотря на обилие функций, лежащих в библиотеках .net framework, некоторые вещи сделать при помощи него все равно принципиально невозможно. Лично мне возможность p/invoke потребовалась, чтобы управлять mandatory level для файлов и named pipes. Проблема состояла в том, что классы контроля доступа .net не понимает sddl-строк, которые содержат описатели mandatory level, и пришлось обращаться к этим функциям напрямую.
Как это выглядит?Достаточно просто. Объявляем статический класс, в нем прописываем статическую функцию с именем той, которую надо экспортировать, составляем соответствующий список параметров, вешаем атрибут dllimport с именем библиотеки dll и радуемся жизни. Пример: импортируем функцию playsound
Основные проблемы
com interopДействительно мощный механизм, который позволяет использовать в managed-коде com-компоненты.
Когда это нужно?В серьезных проектах - достаточно часто. Дело в том, что определенные части системы могут писаться (или уже быть написаны) на С++, Delphi или даже (свят-свят) visual basic (тот что не .net). Кроме того, win32 сама построена на com, а значит, предоставляет доступ к функционалу в том числе и через этот механизм. Для того, чтобы пользоваться им без проблем - следует воспользоваться компонентной моделью и com interop.
Как это выглядит?Достаточно просто. Если ваш объект поддерживает automation - то процесс интеропа пройдет без проблем, если нет - возможно придется помучиться. Чтобы добавить com-объект в сборку, просто подключите его через references проекта. Библиотека, которая маршалит параметры из unmanaged в managed и обратно, создастся автоматически. Естественно, для сложных объектов, например mapi или ad, такой интероп работать не будет, в таком случае, следует воспользоваться подходом, о котором я напишу далее. После того, как вы подключили компонент, просто создайте новый экземпляр объекта и пользуйтесь им так, как будто это родной объект .net.
Нет, мы не создаем объект интерфейса. На самом деле, у этого интерфейса (это видно в метаданных) есть атрибут под названием default coclass, и именно этот объект и создастся.
Основные проблемы
unsafe-контекст.Лично мне кажется ненужным атавизмом, но это лично мое мнение.
Когда это нужно?Чаще всего, применение unsafe-методов мотивируют тем, что подобный подход уменьшает время выполнения кода, особенно в случае работы с большими объемами данных простых типов - в этом случае затраты на boxing/unboxing становятся достаточно велики. Однако, как показывает практика, в реальности программисты, работающие с .net, все больше оперируют сложными объектами вроде dataset, кроме того, unsafe требует определения в манифесте приложения специальных полномочий на выполнение небезопасного кода. Лично у меня необходимости применять unsafe не было никогда - даже задачи маршаллинга сложных типов данных с легкостью выполняются с помощью статического класса marshal с использованием intptr.
Как это выглядит?К описанию метода добавляется ключевое слово unsafe.
При этом в методе становятся доступны операции взятия адреса и механизм работы со ссылками, так же, как в С++.
Основные проблемы
Работа с managed-кодом из unmanaged.Если работа с unmanaged-кодом организована более-менее хорошо, то обратный процесс - обращение к .net сборкам из unmanaged-кода создает огромное количество проблем. Чтобы понять их суть, рассмотрим механизм, который предлагается microsoft для релизации подобного подхода. Механизм этот называется comexport и позволяет экспортировать объекты managed-среды так, как будто это обычные com-объекты. Для экспорта объекта, следует определить для него атрибут comvisible(true), задать clsid, progid, defaultinterface, провести аналогичную операцию для всех экспортируемых интерфейсов. Особенно важным является определение clsid и iid для всех экспортируемых интерфейсов, а также их версий, жестко в коде - иначе при разработке приложения любое изменение интерфейса будет приводить к определению нового clsid и iid, а версия будет постоянно скакать.
Однако если бы все было так радужно - никаких проблем бы не было. Реальность гораздо более грустная и существует ряд проблем, которые могут затруднить использование такого подхода.
Остановимся на последних двух пунктах несколько подробнее. Почему же нельзя создать 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 мягко "не рекомендует" - нельзя. Никогда. Точка. Для этой проблемы существуют два решения:
Когда у меня появилась задача написания namespace extension, которое бы обращалось к серверу, написанному на asp.net, который экспортировал api через механизм wcf, проблема для меня встала действительно остро. Налаживать соединения через сокеты, реализовывать механихм аутентификации windows и парсить soap желания не было, а managed c++ использовать запрещали описанные выше проблемы. Тогда родилась следующая архитектура: namespace extension, написанный на c++, который реализует все интерфейсы windows shell, а за данными обращается к приложению, запущенному на локальной (или удаленной) машине, используя канал named pipe. Из этого подхода родилась общая архитектура подобного рода приложений, названная .net pipe rpc. Подход действительно достаточно общий, однако у меня в Ворде величина этой статьи составляет уже 5 страниц, поэтому я заканчваю, и если хабрачеловеки заинтересовались этой темой - отпишите в комментариях, и я подробно расскажу об этом подходе с примерами реализации. Ссылки по теме
|
|