Язык программирования C# был создан как язык, который можно использовать для простого программирования в ОС Windows. Позже этот язык получил возможность создавать приложения и на других операционных системах, но в этой статье мы будем говорить только об ОС Windows.
Для работы с операционной системой С# использует платформу .Net - по сути, высокоуровневую обертку для WinAPI функций. Для того чтобы выполнить элементарные операции нужно просто найти нужный класс/функцию и передать необходимый набор параметров.
Но всё становится сложнее, если нужно работать с функциями, которые не имеют оберток для .Net или когда требуется низкоуровневый доступ к структурам данных или нативному коду. Статья расскажет о нескольких способах использования функций WinAPI из C#.
managed и unmanaged код
С# используется для разработки программного беспечения поверх платформы .NET. Эта платформа различает несколько версий работы приложения:
managed code
unmanaged code
В первом случае это среда выполнения программ, где есть жествие правила взаимодействия с ресурсами и операционной системой. Тоесть там сущесутвуют механизмы, которые отлавливают ошибки, неверное образение к памяти и помогают программисту сосредоточиться именно на написании алгоритма.
Второй же пример включает в себя традиционное окружение, которое присуще языкам программирования вроде Assembly, C, C++. Там все операции с данными и алгоритмы должны быть четко определены программистом и все неверные действия он должен предусмотреть самостоятельно.
Большое количество библиотек, которые позволяют работать с операционной системой уже переведены на .NET, но есть и те, которые не переведены или не могут быть переписаны в силу особенностей работы алгоритма или наличия команды, которая могла бы портировать код. При разработке ПО может потребоваться использовать такие библиотеки. И в этом случае нужно как-то запускать unmanaged код. Специально для этого в C# существует фича, которая позволяет просто ссылаться на уже собранный файл и зная название и параметры функций просто их вызывать напрямую из библиотек. Фича называется P/Invoke
или Platform Invoke.
С помощью Platform Invoke можно работать с любыми функциями, но необходимо учитывать некоторые особенности. Основная особенность - различие типов. Безопасность C# и платформы .NET в первую очередь строится на введение специальных типов, с которыми работает платформа. Типы, которые применяются в документациях и уже готовых библиотеках могут отличаться и поэтому чтобы была возможность работать со сторонним кодом корректно, нужно определять в коде C# функции специальным образом и проводить маршаллинг
типов данных. Если по-простому это сопоставление аналогичных типов данных между типами данных языка на которм была написана бибилиотека и типами данных, которые есть в C#. Попробуем провести простые эксперименты.
Пример использования PInvoke
Для того чтобы создать приложение, которое будет использовать P/Invoke, создадим новый проект в Visual Studio. Для этого выберем консольное приложение для платформы Windows:
Для работы с P/Invoke нужно добавить using System.Runtime.InteropServices.
И сами функции, так как они будут вызываться за пределами платформы .NET, то их нужно описать с помощью класса DllImportAttribute.
Но класс так же может быть использован для описания дополнительных характеристик, например, можно с помощью атрибута CharSet = CharSet.Unicode явно указать какая кодировка используется для работы функции в приложении. По факту такое описание WinAPI дает возможность .Net платформе задавать статические адреса для вызова алгоритмов функций. Попробуем нарисовать MessageBox и показать его на экране:
И результат выполнения:
Вроде бы ничего сложного, описание метода->сопоставление типов данных->profit. Но это в маленьком проекте, а что делать, если таких функции десятки и больше? Постоянно совмещать типы данных может стать нетривиальной задачей. Специально для этого, ну и для того чтобы помочь комьюнити MS сделали проект, который должен помочь реализовывать все описанные выше действия намного проще.
win32metadata
Уже сейчас существует пререлизная версия проекта, который позволяет в автоматическом режиме генерировать код, который будет создавать определения для функций WinAPI и транслировать типы данных. Этот проект можно найти здесь. Проект официально поддерживается MS и призван упростить использование WinAPI для С#. Вообще проект имеет еще несколько параллельных версий, которые для языков отличных от C# так же предоставляют возможность с помощью нативным методов и типов данных для яхыка программирования использовать WinAPI.
Для того чтобы использовать проект, нужно выполнить ряд рекомендаций:
Visual Studio не ниже 2019
Тип Проекта, который включает в себя либо 5, либо 6 версию .Net. Последняя предпочтительнее
Заходим в директорию проекта и добавляем package:
dotnet add package Microsoft.Windows.CsWin32 --prerelease
Создаем в проекте файл с именем NativeMethods.txt
в этом файле нужно указать название функций. На каждой строке должно быть 1 имя, можно использовать знак *
.
После этого пишем код, который будет его использовать. Несмотря на официальный Readme, нужно использовать namespace Microsoft.Windows.Sdk. Кстати, с новым типом проекта это будет выглядеть вот так:
Результат работы:
Таким образом можно упростить использование WinAPI функций в своем проекте. Это позволит не записывать функции вручную и долго мучаться над транслированием типов данных и сосредоточиться на алгоритме приложения. Кстати, проект обещают развивать и дальше, чтобы можно было пистать более сложные приложения.
В последние рабочие дни хотим анонсировать запуск нового потока курса C# Developer. Professional, который стартует уже в январе.
Узнать подробнее о курсе можно тут.
Комментарии (10)
ArtZilla
28.12.2021 18:14+2Постоянно приходится вызывать unmanaged код для программ на работе, причём не только WinAPI, но и некоторые dll-прослойки на Delphi. Предложенный выше вариант забавный, но ИМХО всё же лучше один раз разобраться с DllImport и научиться самостоятельно писать нужные обёртки.
P.S. Для тех, кому нужно всего-лишь несколько методов из WinAPI: pinvoke.netmayorovp
28.12.2021 20:56+3Если код по обе стороны ваш собственный, то лучше не дублировать всё API через P/Invoke, а воспользоваться COM (если он поддерживается, конечно же). Не полным паттерном, который требует фабрики классов и регистрацию в реестре, а просто выставить наружу одну функцию, которая вернёт или примет интерфейс.
zvszvs
28.12.2021 18:14+1Постоянно совмезать типы данных может стать нетривиальной задачей.
Наверное тут можно упомянуть "Маршалинг": https://docs.microsoft.com/ru-ru/dotnet/standard/native-interop/type-marshaling
NN1
28.12.2021 19:58Я пользуюсь Vanara ( https://github.com/dahall/Vanara ), чуть более высокоуровневая библиотека чем просто обёртка.
korsetlr473
28.12.2021 23:27-1подскажите как взять параметр который отображается в настройках - устройства - блутуз устройства , там у него написан уровень зарядки , вот хочу его вывести около часов
bender1000101011
29.12.2021 13:32Эффективнее писать свою dll на С/C++ с дальнейшем ипортом в C#. Прибегать к P/Invoke, если что то нужно коротенькое прописать из WinApi.
NN1
29.12.2021 18:13Как раз эффективнее в плане скорости разработки и удобства писать всё на C#, а где надо можно код C с указателями и структурами портировать в C#.
Это в случае если у нас не килобайты нативного кода конечно.
Skykharkov
Не реклама, но частенько использую pinvoke.net. Собственно говоря из домена понятно. Несмотря на легкий бардак на их сайте иногда бывает очень полезным. Как правило есть сигнатуры для C# и VB. Но нужно относится критично к примерам. Иногда бывают явные ошибки в сигнатурах.