Комментарии 4
Великолепная статья! Есть вопрос по размеру целочисленных типов: в языках C и C++ размер в байтах таких типов как int, short и тд нефиксированный (есть лишь ограничения, что sizeof(short) <= sizeof(int) <= sizeof(long). В C# напротив размер типов фиксированный. Как мы при вызове функции из dll с параметром типа int можем быть уверенны, что размер int на платформе совпадает с шарповым? То есть с 4 байтами и аналогично вопрос для всех остальных типов?
Спасибо!
Интересный вопрос. Признаюсь, не знал, что в C/C++ размер int зависит от компилятора. Из того, что я выяснил, бегло погуглив:
В контексте статьи (десктопные 64-битные системы) с вероятностью 99.9% размер
intбудет 4 байта, так что и со стороны C++ и со стороны C# размеры совпадут. По крайней мереmsbuildиclangсобирают бинарники с такими параметрами.2-байтные целые числа могут быть при сборке под старые 16-битные ОС или в случае embedding систем. Для полноты стоило бы рассказать про такие случаи, но, честно, я вообще не эксперт в этом.
В C# можно передать
intкак 2-байтное число через такую конструкцию: publicstatic extern void Foo([MarshalAs(UnmanagedType.I2)] int n).
Вообще, при создании декларации для P/Invoke так или иначе мы должны знать ABI нативного файла, иначе мы попросту не сможем вызвать функцию без неожиданных эффектов. Т.е. со стороны C# мы никак не согласуем размеры типов магическим образом, только через явные указания маршалеру.
Задача подготовки декларации для
DllImportтиповая, и материалов касательно P/Invoke триллионы гигабайтов. К чему это я? Да к тому, что это тот случай, когда можно довериться ИИ-ассистентам.
Ещё есть https://github.com/microsoft/CsWin32
Что если скрыть использование ОС-специфичных функций за нашим собственным фасадом, предоставив унифицированный API для использования в .NET-коде? Встречайте — нативный бэкенд.
Я бы пошёл путём вроде того, как сделано в dotnet/runtime:
один класс, но разная имлементация в смежных файлах, например ConsolePal.Windows.cs, ConsolePal.Unix.cs
условный Compile в зависимости от ОС: windows, unix (glob'ы чтобы не прописывать каждый файл, тоже должны работать)
Я бы спускался в unmanaged разве чтобы обернуть в extern "C" какую-нибудь безнадёжно C++'вую библиотеку.
Вы считаете, что Microsoft обошлись только .NET-кодом для работы с разными ОС? Если ошибаюсь, прошу прощения. Если я верно уловил мысль, то вы не правы.
Чтобы вызывать API конкретной операционной системы из .NET, единственный вариант — DllImport/LibraryImport. Да, можно сделать разбивку кода на файлы под разные ОС, но тогда там будут вызовы нативных функций через эти атрибуты, без этого никак в любом случае. И эти вызовы там есть, поищите Interop.Sys в ConsolePal.Unix.cs. Большущий partial-класс Interop содержит тысячи P/Invoke деклараций для системных функций.
Более того, в статье я привёл пример с получением количества входных MIDI-устройств в Linux. Можно накидать кучу P/Invoke деклараций в C# и реализовать в .NET нетривиальную логику выполнения сценария. Но зачем? Почему бы этот сценарий не убрать в нативный фасад, избавив код на C# от беспорядка?
В любом более или менее сложном проекте желание создать нативный бэкенд возникнет, если разработчика заботит какая-никакая чистота кода. Вот, например, в указанном вами репозитории декларация функции Read из System.Native (в этот бинарник компилируется нативный бэкенд .NET), которая как раз используется в Unix-реализации консоли. Код для System.Native разбит по C-файлам в папке src/native.
Ну т.е. решение там комбинированное, но нативнй бэкенд всё равно используется. Я бы очень удивился, если бы они его не сделали. Сама идея с разбивкой какой-то условной логики в .NET любопытная.
https://github.com/microsoft/CsWin32 — интересный проект, спасибо!

Нативный код в .NET-библиотеке и как его готовить