Службы вызова платформы
Эта статья может быть слишком технической для понимания большинства читателей . ( Март 2015 г. ) |
Службы вызова платформы , обычно называемые P/Invoke , представляют собой функцию реализаций Common Language Infrastructure , таких как Microsoft от 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 полезен для использования стандартных (неуправляемых) C или C++ библиотек DLL . Его можно использовать, когда программисту необходим доступ к обширному API Windows , поскольку для многих функций, предоставляемых библиотеками Windows, отсутствуют доступные оболочки . Если API Win32 не предоставляется .NET Framework, оболочку этого API необходимо написать вручную.
Подводные камни
[ редактировать ]Написание оболочек P/Invoke может быть трудным и чревато ошибками. Использование собственных DLL означает, что программист больше не может пользоваться безопасностью типов и сборкой мусора , которые обычно предоставляются в среде .NET. Их неправильное использование может вызвать такие проблемы, как ошибки сегментации или утечки памяти . Получить точные сигнатуры устаревших функций для использования в среде .NET может быть сложно, что может привести к таким проблемам. Для этой цели существуют инструменты и веб-сайты для получения таких подписей, помогающие предотвратить проблемы с подписями. [1]
Другие подводные камни включают в себя:
- Неправильное выравнивание данных пользовательских типов в управляемом языке: существуют разные способы выравнивания данных в зависимости от компиляторов или директив компилятора в C, и необходимо позаботиться о том, чтобы явно указать CLR, как выравнивать данные для непреобразуемых типов . Типичным примером этого является попытка определить тип данных в .NET для объединения в C. представления Две разные переменные перекрываются в памяти, и определение этих двух переменных в типе в .NET приведет к тому, что они будут находиться в разных местах памяти, поэтому для устранения этой проблемы необходимо использовать специальные атрибуты.
- Вмешательство в расположение данных со стороны сборщика мусора управляемого языка: если ссылка является локальной для метода в .NET и передается встроенной функции, то при возврате управляемого метода сборщик мусора может вернуть эту ссылку. Следует позаботиться о том, чтобы ссылка на объект была закреплена , чтобы предотвратить ее сбор или перемещение сборщиком мусора, что может привести к недопустимому доступу со стороны собственного модуля.
При использовании C++/CLI создаваемый CIL может свободно взаимодействовать с объектами, расположенными в управляемой куче, и одновременно с любым адресуемым местоположением собственной памяти. Резидентный объект управляемой кучи можно вызывать, изменять или создавать с помощью простого «объект->поле»; нотация для присвоения значений или указания вызовов методов. Значительный прирост производительности достигается за счет устранения ненужного переключения контекста, а также снижения требований к памяти (более короткие стеки).
Это сопряжено с новыми задачами:
- Код склонен к двойному преобразованию [ 2 ] если не указано конкретно
- загрузчика Проблема с блокировкой [ 3 ]
В этих ссылках указаны решения для каждой из этих проблем, если они возникнут. Основным преимуществом является исключение объявления структуры, порядок объявления полей и проблемы выравнивания отсутствуют в контексте C++ Interop.
Примеры
[ редактировать ]Основные примеры
[ редактировать ]Этот первый простой пример показывает, как получить версию конкретной DLL :
DllGetVersion Сигнатура функции в Windows API :
HRESULT DllGetVersion
(
DLLVERSIONINFO* pdvi
)
Код P/Invoke C# для вызова функции DllGetVersion :
[StructLayout(LayoutKind.Sequential)]
private struct DLLVERSIONINFO {
public int cbSize;
public int dwMajorVersion;
public int dwMinorVersion;
public int dwBuildNumber;
public int dwPlatformID;
}
[DllImport("shell32.dll")]
static extern int DllGetVersion(ref DLLVERSIONINFO pdvi);
Второй пример показывает, как извлечь значок в файл:
Сигнатура функции ExtractIcon в Windows API:
HICON ExtractIcon
(
HINSTANCE hInst,
LPCTSTR lpszExeFileName,
UINT nIconIndex
);
Код P/Invoke C# для вызова функции ExtractIcon :
[DllImport("shell32.dll")]
static extern IntPtr ExtractIcon(
IntPtr hInst,
[MarshalAs(UnmanagedType.LPStr)] string lpszExeFileName,
uint nIconIndex);
Следующий сложный пример показывает, как разделить событие между двумя процессами на платформе Windows :
CreateEvent Сигнатура функции :
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
Код P/Invoke C# для вызова функции CreateEvent :
[DllImport("kernel32.dll", SetLastError=true)]
static extern IntPtr CreateEvent(
IntPtr lpEventAttributes,
bool bManualReset,
bool bInitialState,
[MarshalAs(UnmanagedType.LPStr)] string lpName);
Более сложный пример
[ редактировать ]// native declaration
typedef struct _PAIR
{
DWORD Val1;
DWORD Val2;
} PAIR, *PPAIR;
// Compiled with /clr; use of #pragma managed/unmanaged can lead to double thunking;
// avoid by using a stand-alone .cpp with .h includes.
// This would be located in a .h file.
template<>
inline CLR_PAIR^ marshal_as<CLR_PAIR^, PAIR> (const PAIR&Src) { // Note use of de/referencing. It must match your use.
CLR_PAIR^ Dest = gcnew CLR_PAIR;
Dest->Val1 = Src.Val1;
Dest->Val2 = Src.Val2;
return Dest;
};
CLR_PAIR^ mgd_pair1;
CLR_PAIR^ mgd_pair2;
PAIR native0,*native1=&native0;
native0 = NativeCallGetRefToMemory();
// Using marshal_as. It makes sense for large or frequently used types.
mgd_pair1 = marshal_as<CLR_PAIR^>(*native1);
// Direct field use
mgd_pair2->Val1 = native0.Val1;
mgd_pair2->val2 = native0.val2;
return(mgd_pair1); // Return to C#
Инструменты
[ редактировать ]Существует ряд инструментов, предназначенных для помощи в создании подписей P/Invoke.
Написать служебное приложение, которое импортировало бы заголовочные файлы C++ и собственные файлы DLL и автоматически создавало сборку интерфейса, оказывается довольно сложной задачей. Основная проблема с созданием такого импортера/экспортера для сигнатур P/Invoke — это неоднозначность некоторых типов параметров вызова функций C++.
Брэд Абрамс говорит по этому поводу следующее: [ 4 ]
Проблема заключается в таких функциях C++, как следующие:
__declspec(dllexport) void MyFunction(char *params);
Какой тип нам следует использовать для параметров в нашей сигнатуре P/Invoke? Это может быть либо строка C++, завершающаяся нулем, либо строка массив символов или может быть результатом char параметр . Так следует ли нам использовать нить , Строитель строк , символ [] или ссылка на символ ?
Независимо от этой проблемы, существует несколько инструментов, упрощающих создание подписей P/Invoke.
Один из перечисленных ниже инструментов, xInterop C++ .NET Bridge, решил эту проблему, реализовав несколько переопределений одного и того же метода C++ в мире .NET, после чего разработчики могут выбрать правильный метод для выполнения вызова.
PInvoke.net
[ редактировать ]PInvoke.net — это вики, содержащая сигнатуры P/Invoke для большого количества стандартных API-интерфейсов Windows. Он принадлежит Redgate Software и имеет около 50 000 посещений в месяц.
Подписи создаются вручную пользователями вики. Их можно искать с помощью бесплатной надстройки к Microsoft Visual Studio .
ПИнвокер
[ редактировать ]PInvoker — это приложение, которое импортирует собственные библиотеки DLL и файлы .h C++, а также экспортирует полностью сформированные и скомпилированные библиотеки DLL взаимодействия P/Invoke . Он преодолевает проблему неоднозначности, заключая параметры собственной функции указателя в классы интерфейса .NET, специфичные для PInvoker. Вместо использования стандартных типов параметров .NET в определениях методов P/Invoke ( символ [] , string и т. д.), он использует эти классы интерфейса в вызовах функций P/Invoke.
Например, если мы рассмотрим приведенный выше пример кода, PInvoker создаст функцию .NET P/Invoke, принимающую класс интерфейса .NET, обертывающий собственный символ * указатель. Конструкция этого класса может быть из строка или из массив символов [] . Фактическая собственная структура памяти для обоих одинакова, но соответствующие конструкторы классов интерфейса для каждого типа будут заполнять память по-разному. Таким образом, ответственность за принятие решения о том, какой тип .NET необходимо передать в функцию, передается разработчику.
Помощник по взаимодействию Microsoft
[ редактировать ]Microsoft Interop Assistant — это бесплатный инструмент, имеющий двоичные файлы и исходный код, доступные для загрузки на CodePlex . Он распространяется под лицензией Microsoft Limited Public License (Ms-LPL).
Он состоит из двух частей:
- Конвертер, который принимает небольшие фрагменты собственного кода заголовочного файла C++, содержащего структур определения и методов. Затем он создает код C# P/Invoke, который вы можете скопировать и вставить в свои приложения.
- База данных преобразованных констант, методов и структур Windows API с возможностью поиска.
Поскольку этот инструмент создает исходный код C#, а не скомпилированную dll, пользователь может вносить в код любые необходимые изменения перед использованием. Таким образом, проблема неоднозначности решается тем, что приложение выбирает один конкретный тип .NET для использования в сигнатуре метода P/Invoke, и при необходимости пользователь может изменить его на требуемый тип.
Мастер P/вызова
[ редактировать ]Мастер P/Invoke использует метод, аналогичный Microsoft Interop Assistant, в том, что он принимает собственный код файла C++ .h и создает код C# (или VB.NET), который можно вставить в код приложения .NET.
У него также есть варианты, на какую платформу вы хотите ориентироваться: .NET Framework для настольных компьютеров или .NET Compact Framework для интеллектуальных устройств Windows Mobile (и Windows CE).
xInterop C++ .NET мост
[ редактировать ]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.
Этот инструмент распознает классы шаблонов, которые не экспортируются из DLL C++, создает экземпляр класса шаблона и экспортирует его в дополнительную DLL, а соответствующий интерфейс C++ можно использовать в .NET.
См. также
[ редактировать ]- Преобразуемые типы
- Java Native Interface — стандартный способ доступа Java-программ к собственному коду.
- Java Native Access , Java-эквивалент P/Invoke
- Файлы библиотеки Windows
- J/Direct , эквивалентный API для виртуальной машины Microsoft Java, который больше не поддерживается.
Ссылки
[ редактировать ]- ^ Маршалинг параметров не следует путать с общим термином маршалинг , означающим сериализацию . Маршалированные параметры копируются в стек CLR после их преобразования в типы CTS , но не сериализуются.
- ^ «Двойное преобразование (C++)» .
- ^ «Инициализация смешанных сборок» .
- ^ «Проблема PInvoke» . Learn.microsoft.com . 6 февраля 2004 г.