(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 страниц, поэтому я заканчваю, и если хабрачеловеки заинтересовались этой темой - отпишите в комментариях, и я подробно расскажу об этом подходе с примерами реализации.

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


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

Магазин программного обеспечения   WWW.ITSHOP.RU
TeeChart Pro VCL/FMX with source code single license
Контур.Доступ
ABBYY Lingvo x6 Европейская Профессиональная версия, электронный ключ
VMware Workstation 14 Player for Linux and Windows, ESD
SmartBear QAComplete Concurrent User Subscription License - On Premise (1 Year Subscription)
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Компьютерный дизайн - Все графические редакторы
СУБД Oracle "с нуля"
Новые материалы
Программирование на Visual Basic/Visual Studio и ASP/ASP.NET
Мастерская программиста
Компьютерная библиотека: книги, статьи, полезные ссылки
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100