Доброго времени суток, уважаемые пользователи Хабра.
Я не то что бы профессиональный разработчик на C++, в основном я занимаюсь геймдевом на UE5 (по крайней мере последнее время). Но последнее время достаточно часто я балуюсь разработкой десктоп приложений под Windows. Для красоты и простоты я задумывался об использовании именно react. Но из выбора что я увидел было 2 варианта:
-
Tauri - Фреймворк под Rust с отрисовкой фронтенда сделанном на React и т.п.
-
Electron - фреймворк для разработки кроссплатформенных настольных приложений с использованием веб-технологий (скопировал описание с гугла)
Electron я отмел сразу, так как хотел писать бэкенд на чем то более удобном чем JS/TS для себя. Tauri мне очень понравился, но изучение Rust заняло немного времени. И смотря на это все я подумал, что можно реализовать какой-нибудь аналог Tauri используя C++.
Немного посидев поизучав информацию, я примерно выстроил себе план работы, из этого вылился первый MVP Shine. Фреймворк на данный момент поддерживает только Windows и тесно связан с vcpkg, так как находится на очень ранней стадии разработки, но его уже вполне себе можно потыкать и использовать.
А теперь к установке и использовании:
Для начала то, что нам нужно для запуска:
-
CMake
-
Node.JS
-
CLion (можете использовать что-то своё, мне комфортнее в нём)
Дальше мы открываем терминал и прописываем команду:npm create shine-app@latest
У нас появится простая на данный момент настройка проекта, а именно указание его имени:
Дальше после того как мы введём имя приложение он создаст папку со всем, что нам нужно для реализации приложения
Структура проекта у нас выглядит следующим образом:
D:.
├───cmake # кастомные cmake функции для удобства сборки и запуска
├───frontend # Тут у нас находится фронтенд, сам UI приложения
│ ├───public
│ └───src
│ └───assets
├───generated # Тут у нас лежат ассеты собранные в бинарник, чтобы не таскать их за собой в релизе
├───scripts # Скрипты сборки для удобства
├───shine # Код библиотеки
│ ├───components
│ │ ├───include
│ │ │ └───shine
│ │ │ └───components
│ │ └───src
│ └───core
│ ├───include
│ │ └───shine
│ │ └───engine
│ └───src
│ └───engine
│ └───win32
└───src # Код нашего приложения (в данном случае main.cpp)
На данный момент я не придумал ничего лучше, чем тянуть за собой код библиотеки в пример, чтобы избежать бед со сборкой с разными компиляторами
Дальше мы открываем проект в CLion (или там где удобно вам) и настраиваем профили CMake, У меня это выглядит так:
Дальше идем в View -> Tool Windows -> vcpkg
Далее выбираем наш существующий манифест, и нажимаем на карандашик сверху чтобы он работал с нашими CMake профилями:
Кликаем галочку Add vcpkg integration to existing CMake profiles
Далее кликаем ПКМ по корневому CMakeLists.txt и нажимаем Reload CMake Project.
Дальше мы можем спокойно запустить приложение и увидеть что оно работает:
Идею темплейта я решил взять просто с Tauri, простое окно где можно ввести имя и при нажатии Greet наше C++ ядро отформатирует сообщение и выведет его во фронтенде:
Со стороны C++ наш handler функции greet выглядит следующим образом:
SHINE_COMMAND(greet) {
// У функции собранной с SHINE_COMMAND по дефолту есть аргументы
// В развернутом виде функция выглядит примерно так:
// nlohmann::json greet(const nlohmann::json& args)
std::string name_str = args["name"];
return std::format("Hello, {}", name_str);
}
и указание того, что мы можем вызвать эту функцию из фронтенда:
app.GetRouter().AddHandlers({SHINE_HANDLER(greet)});
После этого наш фронтенд знает что мы можем вызывать функцию greet с помощью метода invoke. Выглядит это следующим образом:
async function handleGreet() {
if (!name.trim()) return;
setIsLoading(true);
try {
// Вызываем функцию и записываем ответ в setGreetMsg
const res = await invoke('greet', { name });
setGreetMsg(res.result || res);
} catch (error) {
setGreetMsg("Error: Could not connect to Shine Core");
} finally {
setIsLoading(false);
}
}
Дальше мы можем как угодно переделывать наш проект React и делать наше UI. При сборке в дебаге приложение просто слушает локалхост с нашим портом, в Shine есть поддержка HMR и при изменении кода, UI в приложении так же изменится. При релизе же, проект фронтенда собирается и после чего переводятся в байты, дальше мы используем эти байты внутри приложения не распаковывая их храним в памяти.
Помимо всего этого у нас есть конфиг файл: shine.conf.json, выглядит он так по стандарту:
{
"window": {
"title": "Shine Secure App",
"width": 900,
"height": 600
},
"capabilities": {
"allowedCommands": [
"fs_read_text_file",
"window_drag"
]
}
}
В нем пока что мы указываем title окна приложения, его размеры. Помимо этого есть ещё 2 параметра конфига:
-
frameless - Отключает рамки у окна
-
resizable - Разрешает / запрещает изменение размера окна
При дебаге конфиг будет подтягиваться из файла, при сборке он так же находится в итоговом приложении, опять же чтобы не тянуть за собой лишние файлы.
Учитывая что мы пишем на C++, и запихиваем ассеты фронтенда прямиком в бинарник, приложение из template имеет достаточно приятный вес - всего 845кб, при этом он не требует зависимостей для запуска и можно отправлять голый .exe пользователю и он запустится на его системе.
В планах у меня реализовать поддержку Linux и MacOS, а так же возможность получать состояние элементов фронтенда из плюсов и взаимодействия с ними. Например заполнять прогресс бар условный из C++ при какой-нибудь загрузке и т.п.
Это моя первая попытка сделать что-либо полезное на C++ для опенсорса, буду очень рад объективной критике, советам и контрибьютингу.
Автор: wtf-keaton
