Подтвердить что ты не робот

С# Native Interop - Почему большинство библиотек используют LoadLibrary и делегаты вместо SetDllDirectory и простой DllImport

отличный ответ на SO о том, как установить каталог поиска DllImport во время выполнения. Работает отлично с двумя строками кода.

Однако многие проекты с открытым исходным кодом вместо этого используют функцию LoadLibrary. Есть "слухи" о том, что вызов собственных методов через делегатов происходит медленнее. Я называю их "слухами", потому что я видел это только в двух местах, и это все равно микро-оптимизация.

Самое интересное место в этом блоге: http://ybeernet.blogspot.com/2011/03/techniques-of-calling-unmanaged-code.html

Там автор измерил производительность разных методов:

  • С# (информативный) 4318 мс
  • PInvoke - сдерживаемая защита 5415 мс
  • Инструкция Calli 5505 мс
  • С++/CLI 6311 мс
  • Функция делегирования - сдерживаемая защита 7788 мс
  • PInvoke 8249 мс
  • Делегат функций 11594ms

NNanomsg использует делегаты функций, но упоминает запись в блоге с комментарием "Влияние производительности на обычный P/Invoke, очевидно, не хорошо" на эта строка.

сервер Kestrel от MSFT ASP vNext использует ту же технику с библиотекой Libuv: вот код

Я думаю, что делегаты более громоздки в использовании, чем простой DllImport, и учитывая разницу в производительности, мне интересно, почему ориентированные на производительность библиотеки используют делегатов вместо установки папки поиска dll?

Существуют ли какие-либо технические причины, такие как безопасность, гибкость или что-то еще, или это просто вопрос вкуса? Я не понимаю обоснования - возможно ли, что авторы просто не искали StackOverflow достаточно??

4b9b3361

Ответ 1

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

Здесь сравниваются два принципиально разных типа сценариев взаимодействия. Первый - это "обычный", управляемый код вызова программы в неуправляемой DLL. Использование атрибута [DllImport] или С++/CLI - это оружие по выбору. Очень высоко оптимизированная внутри CLR, она динамически генерирует машинный код, который переводит аргументы и выполняет вызов. Важно, управляемая программа всегда запускает много неуправляемого кода, учитывая, что он работает поверх чистой неуправляемой операционной системы.

То, о чем вы говорите, "медленная" версия, идет другим путем. Вызов управляемого кода из неуправляемой программы. Некоторые люди называют этот "обратный pinvoke". Это намного сложнее, потому что прежде чем вы можете вызвать управляемый код, вам сначала нужно загрузить CLR и инициализировать. И создать appdomain. И найдите и загрузите сборку .NET, содержащую код. И JIT скомпилирует его.

Существует три основных способа сделать это:

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

  • Откройте классы .NET как COM-компоненты, указав атрибут [ComVisible(true)]. Очень простой способ получить неуправляемый код полностью не осознает, что на самом деле он использует .NET-код. Хост CLR по умолчанию загружается, запись в реестре для компонента COM указывает на mscoree.dll, который при необходимости загружает CLR. Единственным недостатком является неуправляемый автор кода для написания COM-кода клиента, навык, который теряется.

  • О чем вы говорите, используя возможность компилятора С++/CLI генерировать экспорт DLL. Примечателен также для использования инструментом Robert Gieseke Unmanaged Exports, используя ту же технику, но вставляя эти DLL-экспорт, переписывая сборку.

Есть очень значительные недостатки, чтобы сделать это третьим способом, за счет вызова. Он плохо масштабируется, каждый отдельный метод должен быть явно экспортирован, и он должен быть статическим, поэтому вы не можете реализовать объектную модель. И проблема супер-пупер, ужасная, противная, невозможная для решения проблемы, когда вы не можете получить какую-либо диагностику при сбое вызова. Управляемый код любит бросать исключения, если не из самого кода, а затем из CLR, который пытается сказать вам, что вы передали неправильные аргументы или не можете получить подготовленный код. Вы не можете видеть эти исключения, нет способа сказать, что функция не удалась, и способ объяснить, почему она не удалась. Если неуправляемый код не обнаруживает исключения SEH с нестандартными __try/__ минусами, то программа бомбит. Никакой диагностики вообще. Даже если он поймает SEH, вы получите сигнал "это не работает".

Управляемый код, который вызывается таким образом, должен быть написан для решения этой проблемы. Он должен содержать try/catch-em-all в общедоступном методе. И запишите исключение и предоставите способ вернуть код ошибки, чтобы вызывающий мог обнаружить сбой. Однако общие проблемы, такие как отсутствие зависимых библиотек DLL или проблемы с версиями, вообще не диагностируются. Это выглядит легко для неуправляемого автора кода, простой LoadLibrary + GetProcAddress, но это долговременный кошмар поддержки.