Язык программирования 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)


  1. Skykharkov
    28.12.2021 17:53
    +8

    Не реклама, но частенько использую pinvoke.net. Собственно говоря из домена понятно. Несмотря на легкий бардак на их сайте иногда бывает очень полезным. Как правило есть сигнатуры для C# и VB. Но нужно относится критично к примерам. Иногда бывают явные ошибки в сигнатурах.


  1. ArtZilla
    28.12.2021 18:14
    +2

    Постоянно приходится вызывать unmanaged код для программ на работе, причём не только WinAPI, но и некоторые dll-прослойки на Delphi. Предложенный выше вариант забавный, но ИМХО всё же лучше один раз разобраться с DllImport и научиться самостоятельно писать нужные обёртки.
    P.S. Для тех, кому нужно всего-лишь несколько методов из WinAPI: pinvoke.net


    1. mayorovp
      28.12.2021 20:56
      +3

      Если код по обе стороны ваш собственный, то лучше не дублировать всё API через P/Invoke, а воспользоваться COM (если он поддерживается, конечно же). Не полным паттерном, который требует фабрики классов и регистрацию в реестре, а просто выставить наружу одну функцию, которая вернёт или примет интерфейс.


  1. zvszvs
    28.12.2021 18:14
    +1

    Постоянно совмезать типы данных может стать нетривиальной задачей.

    Наверное тут можно упомянуть "Маршалинг": https://docs.microsoft.com/ru-ru/dotnet/standard/native-interop/type-marshaling


    1. zvszvs
      28.12.2021 18:19

      Не заметил сразу. Там выше упоминается.


  1. NN1
    28.12.2021 19:58

    Я пользуюсь Vanara ( https://github.com/dahall/Vanara ), чуть более высокоуровневая библиотека чем просто обёртка.


  1. korsetlr473
    28.12.2021 23:27
    -1

    подскажите как взять параметр который отображается в настройках - устройства - блутуз устройства , там у него написан уровень зарядки , вот хочу его вывести около часов


    1. m0tral
      29.12.2021 13:31

      BLE вам в помощь, никакого unmanaged там не нужно


  1. bender1000101011
    29.12.2021 13:32

    Эффективнее писать свою dll на С/C++ с дальнейшем ипортом в C#. Прибегать к P/Invoke, если что то нужно коротенькое прописать из WinApi.


    1. NN1
      29.12.2021 18:13

      Как раз эффективнее в плане скорости разработки и удобства писать всё на C#, а где надо можно код C с указателями и структурами портировать в C#.

      Это в случае если у нас не килобайты нативного кода конечно.