Платформа Призвание Услуга , как правило , называют P / Invoke , является особенностью Common Language Infrastructure реализаций, как Microsoft «s Common Language Runtime , что позволяет управляемый код для вызова собственного кода .
Управляемый код, такой как C # или VB.NET, обеспечивает собственный доступ к классам, методам и типам, определенным в библиотеках, составляющих .NET Framework. Хотя .NET Framework предоставляет обширный набор функций, ему может не хватать доступа ко многим библиотекам операционной системы нижнего уровня, обычно написанным в неуправляемом коде, или сторонним библиотекам, также написанным в неуправляемом коде. P / Invoke - это метод, который программист может использовать для доступа к функциям в этих библиотеках. Вызов функций в этих библиотеках происходит путем объявления сигнатуры неуправляемой функции в управляемом коде, которая служит фактической функцией, которую можно вызывать, как и любой другой управляемый метод. Объявление ссылается на путь к файлу библиотеки и определяет параметры функции и возвращаемые данные в управляемых типах, которые, скорее всего, будут неявно маршалированы в и из неуправляемых типов средой CLR. Когда неуправляемые типы данных становятся слишком сложными для простого неявного преобразования из и в управляемые типы, инфраструктура позволяет пользователю определять атрибуты функции, возврата и / или параметров для явного уточнения того, как данные должны быть маршалированы, чтобы приводить к исключениям при попытке сделать это неявно. Программистам с управляемым кодом доступно множество абстракций концепций программирования нижнего уровня по сравнению с программированием на неуправляемых языках. В результате программист, имеющий только опыт работы с управляемым кодом, должен будет освежить в памяти концепции программирования, такие как указатели, структуры и передача по ссылке, чтобы преодолеть некоторые из более простых, но общих препятствий при использовании P / Invoke.
Архитектура
Обзор
В настоящее время используются два варианта P / Invoke:
Явный
- Собственный код импортируется через библиотеки с динамической компоновкой (DLL).
- Метаданные, встроенные в сборку вызывающей стороны, определяют, как должен быть вызван собственный код и обеспечен доступ к данным ( обычно требуются спецификаторы атрибутированного источника, чтобы помочь компилятору в создании маршал-клея )
- Это определение является «явной» частью
Скрытый
- Используя C ++ / CLI , приложение может одновременно использовать управляемую кучу (посредством указателей отслеживания) и любую область собственной памяти без явного объявления. (Скрытый)
- Основное преимущество в этом случае состоит в том, что при изменении базовых собственных структур данных при совместимости именования можно избежать критических изменений .
- т.е. добавление / удаление / изменение порядка структур в собственном заголовке будет прозрачно поддерживаться до тех пор, пока имена членов структуры не изменились.
Подробности
При использовании P / Invoke среда CLR обрабатывает загрузку DLL и преобразование неуправляемых предыдущих типов в типы CTS (также называемое маршалингом параметров ). [1] [ необходима цитата ] Для этого CLR :
- Находит DLL, содержащую функцию.
- Загружает DLL в память.
- Находит адрес функции в памяти и помещает ее аргументы в стек , при необходимости маршалируя данные.
P / Invoke полезен для использования стандартных (неуправляемых) библиотек DLL C или C ++ . Его можно использовать, когда программисту требуется доступ к обширному API Windows , поскольку многие функции, предоставляемые библиотеками Windows, не имеют доступных оболочек . Если Win32 API не предоставляется .NET Framework, оболочку для этого API нужно писать вручную.
Ловушки
Написание оболочки P / Invoke может быть трудным и подверженным ошибкам. Использование собственных библиотек DLL означает, что программист больше не может пользоваться преимуществами безопасности типов и сборки мусора, которые обычно предоставляются в среде .NET. Когда они используются ненадлежащим образом, это может вызвать такие проблемы, как ошибки сегментации или утечки памяти . Получение точных сигнатур унаследованных функций для использования в среде .NET может быть трудным, что может привести к таким проблемам. Для этого существуют инструменты и веб-сайты для получения таких подписей, помогающие предотвратить проблемы с подписями. [1]
К другим подводным камням относятся:
- Неправильное выравнивание данных определяемых пользователем типов на управляемом языке: существуют разные способы выравнивания данных в зависимости от компиляторов или директив компилятора в C, и необходимо явно указать CLR, как выравнивать данные для непреобразуемых типов . Типичным примером этого является то, при попытке определить тип данных в .NET , чтобы представлять объединение в C . Две разные переменные перекрываются в памяти, и определение этих двух переменных в типе в .NET приведет к тому, что они будут находиться в разных местах в памяти, поэтому для устранения проблемы необходимо использовать специальные атрибуты.
- Вмешательство сборщика мусора управляемого языка в расположение данных: если ссылка является локальной для метода в .NET и передается встроенной функции, когда управляемый метод возвращается, сборщик мусора может вернуть эту ссылку. Следует позаботиться о том, чтобы ссылка на объект была закреплена , чтобы предотвратить ее сбор или перемещение сборщиком мусора, что может привести к недопустимому доступу для собственного модуля.
При использовании C ++ / CLI испускаемый CIL может свободно взаимодействовать с объектами, расположенными в управляемой куче, и одновременно с любой адресуемой собственной ячейкой памяти. Резидентный объект управляемой кучи может быть вызван, изменен или сконструирован с использованием простого "объект-> поле;" нотация для присвоения значений или указания вызовов методов. Значительный прирост производительности достигается за счет устранения ненужного переключения контекста, уменьшения требований к памяти (более короткие стеки).
Это связано с новыми проблемами:
- Код подвержен двойному преобразованию [2], если не указано иное.
- Проблема с блокировкой загрузчика [3]
Эти ссылки определяют решения для каждой из этих проблем, если они встречаются. Основным преимуществом является устранение объявления структуры, порядка объявления полей и проблем с выравниванием, которые отсутствуют в контексте взаимодействия C ++.
Примеры
Основные примеры
Этот первый простой пример показывает, как получить версию конкретной DLL :
Сигнатура функции DllGetVersion в Windows API :
HRESULT DllGetVersion ( DLLVERSIONINFO * pdvi )
P / Invoke код C # для вызова функции DllGetVersion :
[StructLayout (LayoutKind.Sequential)] частная структура DLLVERSIONINFO { public int cbSize ; public int dwMajorVersion ; public int dwMinorVersion ; public int dwBuildNumber ; общедоступный int dwPlatformID ; } [DllImport ("shell32.dll")] static extern int DllGetVersion ( ссылка DLLVERSIONINFO pdvi );
Во втором примере показано, как извлечь значок в файл:
Сигнатура функции ExtractIcon в Windows API:
HICON ExtractIcon ( HINSTANCE hInst , LPCTSTR lpszExeFileName , UINT nIconIndex );
P / Invoke код C # для вызова функции ExtractIcon :
[DllImport ( "shell32.dll")] статическая ехЬегп IntPtr ExtractIcon ( IntPtr hInst , [MarshalAs (UnmanagedType.LPStr)] строка lpszExeFileName , UINT nIconIndex );
В следующем сложном примере показано, как разделить событие между двумя процессами на платформе Windows :
Сигнатура функции CreateEvent :
РУЧКА CreateEvent ( LPSECURITY_ATTRIBUTES lpEventAttributes , BOOL bManualReset , BOOL bInitialState , LPCTSTR lpName );
P / Invoke код C # для вызова функции CreateEvent :
[DllImport ("kernel32.dll", SetLastError = true)] статический extern IntPtr CreateEvent ( IntPtr lpEventAttributes , bool bManualReset , bool bInitialState , [MarshalAs (UnmanagedType.LPStr)] строка lpName );
Более сложный пример
// собственное объявление typedef struct _PAIR { DWORD Val1 ; DWORD Val2 ; } ПАРА , * PPAIR ;
// Скомпилировано с помощью / clr; использование #pragma managed / unmanaged может привести к двойному преобразованию; // Избегайте использования автономного .cpp с .h includes. // Это будет находиться в файле .h.template <> inline CLR_PAIR ^ marshal_as < CLR_PAIR ^ , PAIR > ( const PAIR & Src ) { // Обратите внимание на использование de / referencing. Он должен соответствовать вашему использованию. CLR_PAIR ^ Dest = gcnew CLR_PAIR ; Dest -> Val1 = Src . Val1 ; Dest -> Val2 = Src . Val2 ; return Dest ; };
CLR_PAIR ^ mgd_pair1 ; CLR_PAIR ^ mgd_pair2 ; ПАРА native0 , * native1 = & native0 ;native0 = NativeCallGetRefToMemory ();// Использование marshal_as. Это имеет смысл для больших или часто используемых типов. mgd_pair1 = marshal_as < CLR_PAIR ^> ( * native1 );// Прямое использование поля mgd_pair2 -> Val1 = native0 . Val1 ; mgd_pair2 -> val2 = native0 . val2 ;возврат ( mgd_pair1 ); // Вернуться на C #
Инструменты
Существует ряд инструментов, предназначенных для помощи в создании подписей P / Invoke.
Написание служебного приложения, которое будет импортировать файлы заголовков C ++ и собственные файлы DLL и автоматически создавать сборку интерфейса, оказывается довольно сложной задачей. Основная проблема с созданием такого импортера / экспортера для сигнатур P / Invoke - неоднозначность некоторых типов параметров вызова функций C ++.
Брэд Абрамс сказал по этому поводу следующее: Проблема P / Invoke .
Проблема заключается в следующих функциях C ++:
__declspec ( dllexport ) void MyFunction ( char * params );
Какой мы должны использовать для параметра Params в нашем P / Invoke подписи? Это может быть либо строка C ++ с завершающим нулем, либо массив символов, либо выход параметр char . Так следует ли нам использовать строка , StringBuilder , char [] или ref char ?
Независимо от этой проблемы, есть несколько инструментов, позволяющих упростить создание подписей P / Invoke.
Один из перечисленных ниже инструментов, xInterop C ++ .NET Bridge , решил эту проблему, реализовав несколько переопределений одного и того же метода C ++ в мире .NET, после чего разработчики могут выбрать правильный метод для вызова.
PInvoke.net
PInvoke.net - это вики, содержащая подписи P / Invoke для большого количества стандартных API Windows. Он принадлежит Redgate Software и имеет около 50000 посещений в месяц.
Подписи создаются вручную пользователями вики. Их можно искать с помощью бесплатного дополнения к Microsoft Visual Studio .
PInvoker
PInvoker - это приложение, которое импортирует собственные библиотеки DLL и файлы .h C ++ и экспортирует полностью сформированные и скомпилированные библиотеки DLL взаимодействия P / Invoke . Он преодолевает проблему двусмысленности, оборачивая параметры функций собственных указателей в классы интерфейса .NET, специфичные для PInvoker. Вместо использования стандартных типов параметров .NET в определениях методов P / Invoke ( char [] , string и т. д.) он использует эти классы интерфейса в вызовах функций P / Invoke.
Например, если мы рассмотрим приведенный выше пример кода, PInvoker создаст функцию .NET P / Invoke, принимающую класс интерфейса .NET, обертывающий собственный char * указатель. Построение этого класса могло бы происходить из строка или из char [] массив. Фактическая структура собственной памяти для обоих одинакова, но соответствующие конструкторы класса интерфейса для каждого типа будут заполнять память по-разному. Таким образом, ответственность за принятие решения о том, какой тип .NET необходимо передать функции, передается разработчику.
Помощник по взаимодействию с Microsoft
Microsoft Interop Assistant - это бесплатный инструмент, содержащий двоичные файлы и исходный код, доступные для загрузки на CodePlex . Он находится под лицензией Microsoft Limited Public License (Ms-LPL).
Он состоит из двух частей:
- Конвертер, который принимает небольшие фрагменты исходного кода файла заголовка C ++, содержащие определения структур и методов. Затем он создает код C # P / Invoke, который вы можете скопировать и вставить в свои приложения.
- База данных с возможностью поиска преобразованных определений констант, методов и структур Windows API.
Поскольку этот инструмент создает исходный код C #, а не скомпилированную dll, пользователь может свободно вносить в код любые изменения перед использованием. Таким образом, проблема неоднозначности решается тем, что приложение выбирает один конкретный тип .NET для использования в сигнатуре метода P / Invoke, и при необходимости пользователь может изменить его на требуемый тип.
P / Invoke Wizard
Мастер P / Invoke Wizard использует метод, аналогичный Microsoft Interop Assistant, в котором он принимает собственный код файла C ++ .h и создает код C # (или VB.NET), который вы можете вставить в код приложения .NET.
У него также есть варианты, на какую платформу вы хотите ориентироваться: .NET Framework для настольных ПК или .NET Compact Framework для интеллектуальных устройств Windows Mobile (и Windows CE).
xInterop C ++ .NET Bridge
xInterop C ++ .NET Bridge - это приложение Windows для создания оболочки C # для собственных библиотек DLL C ++ и моста C ++ для доступа к сборкам .NET, оно поставляется с библиотекой C # /. NET, которая обертывает стандартные классы C ++, такие как string, iostream и т. д. , Классы и объекты C ++ доступны из .NET.
Этот инструмент генерирует библиотеки DLL оболочки C # с исходным кодом из существующих собственных библиотек DLL C ++ и связанных файлов заголовков, которые требуются инструменту для создания библиотеки DLL оболочки C #. Подписи P / Invoke и маршалинг данных генерируются приложением. Полученная оболочка C # имеет интерфейс, аналогичный интерфейсу C ++, с типом параметра, преобразованным в код .NET.
Этот инструмент распознает классы шаблонов, которые не экспортируются из C ++ DLL, создает экземпляр класса шаблона и экспортирует его в дополнительную DLL, а соответствующий интерфейс C ++ может использоваться в .NET.
Смотрите также
- Непреобразуемые типы
- Собственный интерфейс Java , стандартный способ для программ Java доступа к машинному коду
- Java Native Access , Java-эквивалент P / Invoke
- Файлы библиотеки Windows
- J / Direct , более не поддерживаемый эквивалентный API для виртуальной машины Microsoft Java
Рекомендации
- ^ Маршалинг параметров не следует путать с общим термином маршалинг , означающим сериализацию . Маршалированные параметры копируются встек CLR после их преобразования втипы CTS , но не сериализуются.
- ^ https://docs.microsoft.com/en-us/cpp/dotnet/double-thunking-cpp
- ^ https://docs.microsoft.com/en-us/cpp/dotnet/initialization-of-mixed-assemblies
Внешние ссылки
- Сайт, посвященный P / Invoke
- J / Invoke Java-доступ к Win32 API или разделяемым библиотекам Linux / Mac OS X, аналогично P / Invoke
- [2] Неявный P / Invoke с особым упором на методы расширения до шаблона маршалинга.
- 3 статьи от Microsoft, в которых сравниваются эти методы : Использование явного вызова PInvoke, неявного взаимодействия C ++ и «Более пристальный взгляд на вызов платформы».
- Microsoft Interop Assistant Главная страница Microsoft Interop Assistant.
- P / Invoke Wizard P / Домашняя страница Invoke Wizard.
- PInvoker Главная страница PInvoker.
- xInterop C ++ .NET Bridge главная страница xInterop C ++ .NET Bridge