Pull to refresh

Comments 31

Вызов unsafe не запрещен конечно, но вот одна из задач разработчика в Rust – изолировать такие конструкции, гарантировав на уровне обертки безопасность вызова.
Поскольку же Windows API один сплошной unsafe, от использования Rust тут, как по мне, мало толку.

Зависит от задачи. Если вам нужен нативный гуй под окна, никуда от WinAPI не деться. WinUI до продакшена еще не доплыл, а обертки разной степени скисшести все равно обмазывают внутренности тех же вызовов ансейфом.

Ну да, тут момент в том, что пока ты сидишь в тёплой и уютном rust, то всё хорошо и можно жить без unsafe.

Но как только ты выходишь за пределы уютненького, то тут как не крути, без unsafe разобраться нельзя.

Можно напилить максимально идиоматичный гуй прямо на Rust. Он будет, вестимо, что-то за собой тащить - графресурсы, OpenGL, логику взаимодействия и отрисовки, и выглядеть как инопланетянин.

Простите, может быть я не знаю правильной библиотеки, но вот опять-же. Читаю про опенгжель на расте, и нахожу следующее:

Why “unsafe” exists

It may seem strange that “unsafe” exists at all. The reason for it is quite simple: it allows us to deal with complicated stuff once, inside a function with a safe API, and then completely forget about it when we become the users of that API. In other words, it moves the responsibility of correct API usage to API implementer.

То есть всё то же самое. Или я где-то пропускаю rust-native opengl?

Не совсем. Если весь гуй написан на Rust, то на unsafe остаются только неотвратимые системные вызовы: окно создать, кадр нарисовать, кнопки послушать. Какой-бы ни была Rust-прокладка, она все равно на каждый вызов упирается в итоге в unsafe FFI. Просто в гуе, нарисованном на Rust, таких вызовов будет сильно меньше, чем в системном гуе, который из Rust вызывается.

Вы так считаете?

Ну вот посмотрите, у вас есть процедура обработки сообщений которая unsafe. Из неё вы просто вызываете safe функции да и всё.

А писать алгоритм изменения размера окна на открытой гжели вручную, тут вам как ни крути, намного больше FFI потребуется.

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

Можно придраться, что для низкоуровневого рисования надо сильно больше одного ffi на кадр. Для окна с одной кнопкой - возможно ffi будет больше у саморисовки, за счет настройки графики, кадра, итп. Если кнопок под сотню, как в микрософт офисе, перевес легко может быть в другую сторону.

Хммм.. А у вас нет проекта в гитхабе, чисто чтобы посмотреть?

Для саморисовки через WinGDI вот. Это вообще весь код, количество вызовов ffi постоянно и не зависит от количества рисуемого.

https://github.com/snuk182/nuklear-backend-gdi/blob/master/src/lib.rs

Для системных контролов вот. Оцените количество ансейфа.

https://github.com/plyhun/plygui-win32

Ваш аргумент считаю достойным и удовлетворительным. Спасибо за код.

Блин. Прошу меня извинить. Из всех вариантов отрисовки Nuklear я выбрал самый неудачный. В случае именно WinGDI количество ffi таки зависит от количества контролов, потому что графпримитивы контролов рисуются средствами GDI, зато на кадр влияния минимум, а должно быть наоборот - сначала весь гуй компонуется в битмап кадра, а потом сам кадр отправляется через ffi на отрисовку. Правильно сделано вот здесь (биндинги к OpenGL):

https://github.com/snuk182/nuklear-backend-glium/blob/master/src/lib.rs

Также тут (биндинги к Vulkan/DX11).

https://github.com/snuk182/nuklear-backend-wgpurs/blob/master/src/lib.rs

А как же логика вашей программы?

Winapi существовал со времён царя Гороха. Обычно, повторюсь, обычно, он не падает. Когда вы последний раз видели падение из за нажатия на кнопку. (не кода самой кнопки, а именно Windows который эту кнопку визуально опускает вниз на экране и поднимает)

Плюс, опять же, каковы ваши альтернативы? Писать на сях? А потом сидеть и пытаться выяснить, в каком pinvoke вы не разыменовали шестой указатель в какую процедуру, или где не добавили очередную обёртку, которая преобразовала бы вашу строку во что-то ещё?

Или у вас есть стабильная, проверенная временем система работы с GUI в Rust? Или мы щас вот прямо Electron туда запихнём?

UFO just landed and posted this here

А скажите, каким Макаром это связано со статьёй? Писать маленькие бинарники это было потехой со времён бояр при Иване Грозном. Но они же ничего не делают.

А вот что-то работающее - это другое дело.

UFO just landed and posted this here

Но я то объявил конкурс на самый маленький футпринт в памяти. А не на диске. Так что тут такие дела. Сколько она на диске займёт мне не важно. Плюс, одно из условий конкурса - это графический интерфейс.

Возможно можно будет стандартными шейдерами DirectX запилить его. Вот тут, если помните в 2004 году была .kkrieger?

А с переводом — почему бы и нет? Вы читать будете? Если да, то я переведу 8-)

UFO just landed and posted this here

Ну, по этому поводу, я могу вам сказать одно: Я как писал про код, так и буду писать про код.

Ох, из чего ж статью то высосали, а?

По сути, написали на том же Си, но с синтаксисом другого языка, абсолютно не разобравшись в языке.

unsafe в этом случае нужен для того, чтобы сделать safe-обёртку, RAII и т.п. Ваш же код ни на йоту не безопаснее кода на Си. Даже наоборот: unsafe во всех местах, а не в конкретных идиоматических -- верный генератор UB похлеще С++.

Для начала, нам потребуется rust nightly

Что-то поменялось? windows-rs всегда без проблем работал со stable.

в нём есть библиотека bindings. Цель этой библиотеки — подключать зависимости для того, чтобы вы могли работать с Windows API в вашем приложении.

Цель этой библиотеки -- вынести биндинги в отдельный юнит для компилятора, чтобы каждый раз не запускать кодогенерацию и компиляцию. И не подключает она никаких зависимостей, она генерирует из метаданных обёртки, типы и адаптеры для удобного вызова extern функций. И да, это не биндинги в привычном их понимании, а именно wrapper'ы, которые, по опыту использования, очень хреново инлайнятся и кучу неявных side-эффектов генерят. Например, получая адрес функции, получаешь адрес незаинлайненного thumb wrapper'а, даже если фактический вызов заинлайнился, то есть, по сути, адрес мёртвого кода. При этом адрес получить можно только полным повторным extern fn объявлением для каждой WinAPI функции. Конечно, адреса нужны не всегда, но иногда нужно и много, не зря же Раст -- всё таки системный низкоуровневый язык.

Это — вызов макроса, который подключит все необходимые зависимости.

Опять таки, никаких зависимостей он не подключает, а просто инлайнит сгенерированный из метадаты исходник на Расте из стандартной директории выхлопа генератора.

Первое — функция main теперь должна возвращать windows::Result<()>

2. fn main должна возвращать результат, понятный Windows

Не выдумывайте, откуда вы взяли такое требование? main может возвращать любой тип, который реализует трейт std::process::Termination, хоть пустую юнит-стуктуру делайте и возвращайте из неё 42, никто не запрещает. Похоже, вы скопипастили из примеров код, не разобравшись, зачем там этот тип указан. А указан он для использования оператора ? на std::ops::Try-типах, то есть там, где возвращается либо COM'овский Result, содержащий WinRT'шную ошибку, либо растовый Result, полученный через какой угодно метод конвертации. Вы же ни разу этот оператор не использовали, значит тип возврата main тут не причём и может быть любым impl Termination.

Пишете, что нужно везде использовать W-версии функций, а сами используете A-версии.

debug_assert!(instance.0 != 0);

Доступ в кишки тупла -- такое себе. В windows-rs эта проверка делается через windows::Handle::is_invalid().

let atom = RegisterClassA(&wc);

deprecated же. RegisterClassEx сейчас рекомендуется использовать.

HWND(0)

То, что HWND == NULL и HWND == 0 имеют одинаковые значения -- чисто случайное implementation defined совпадение и потенциальное UB. В windows-rs трейт std::default::Default мапится на NULL handle.

extern "system" fn wndproc(...){ unsafe{...

Вся функция здесь -- unsafe. Нужно не блок внутри функции делать unsafe, а всё extern-объявление, ибо unsafe интерфейс и unsafe реализация -- разные вещи.

match message as u32

В чём сакральный смысл конвертации u32 в u32?

MessageBoxA(None, "Привет", "Это игра 2048 для Хабры", MB_OK | MB_ICONINFORMATION );

Поэтому нам не нужно извращаться и писать что-то типа:

MessageBoxA(std::ptr::null_mut(), ...

Здесь не стоит путать ABI указателя на ffi функцию и его мапинг из Option'а с костылём, который придумали в windows-rs. Это совершенно разные вещи. Из коробки работает толькоOption<extern fn()>. Другие типы без бубнов так не работают, нужны обёртки из трейтов.

Уважаемый, вводная же статья. Первая доза бесплатно. Зачем распугиваешь всех? Оно понятно что про дебри FFI и возвращаемые значения в виде писать можно долго. Но не прям с карьера.

Приходите, пишите. Будет полезно.

Всё же очень полезные замечания. Не надо так.

На Win32 API писать скучно, муторно и одноплатформенно. Вот интересно на Sciter или Tauri.

из-за таких как вы современный софт деградирует.

UFO just landed and posted this here

Спасибо за статью! Как правильно написал TrueBers, стоило упомянуть, что windows-rs базируется на подходе language projection - автоматическая генерация идиоматической для языка библиотеки из метаданных - который применяется также для представления Windows API в C++ и в С#. То есть это позволяет надеяться, что проект останется актуальным надолго.

Про nightly у меня тоже большие сомнения - я конечно на последнюю версию windows-rs давно не переходил, но с чего бы им от stable отказываться?

Ну и про 2048 - вот для затравки мой вариант https://github.com/milyin/game2048-rs :-)

Вот интересно. Здесь `windows-rs` наколбасил генерированных биндингов, и отдает их все сразу. У соседнего крейта `winapi-rs` подход к линковке другой - надо явно указать в фичах неймспейс, и тогда он будет подлинкован. Насколько такой подход с разделением оправдан?

winapi-rs я не трогал, но думаю, что он пишется руками. Т.е. там будет только то, что разработчики написали.

У windows-rs же подход другой - они генерируют обертку к любым COM-объектам, API которых описан в .winmd файлах. Например у себя в game2048-rs я подключаю win2d прямо из DLL-ей, которые лежат вместе с проектом - windows-rs подхватывает их из https://github.com/milyin/game2048-rs/tree/master/.windows.

Хмммм.... Хмммм.... Лезем разбираться. Красиво у вас получается. Вы, судя по всему, взялись за получение первого места? :-)

Sign up to leave a comment.