Спойлер для экономии времени читающих — ниже мы используем в Расте библиотеку GUI из NI LabWindows/CVI. Практическая польза мероприятия стремится к нулю (ниже вы поймёте почему), это просто эксперимент.
Прежде чем мы продолжим, небольшой дисклеймер — мы будем "паразитировать" на коммерческом продукте, к которому я не имею ровно никакого отношения, за исключением того, что просто являюсь пользователем, абсолютно никакой рекламной нагрузки пост не несёт, хотя я буду обильно сопровождать изложение ссылками. Я, пожалуй, добавлю хаб "Ненормальное программирование", хотя как вы увидите ниже, ничего особо ненормального там не будет, мы просто "скрестим ужа и ежа".
Прелюдия
Вообще говоря началась эта история с рутинной задачки по рефакторингу одного знатного легаси приложения под Windows, в котором надо было переложить древний код, написанный на Си, на свежую Студию 2026, попутно переделав на С++ в целях дальнейшего развития продукта. Показать сам проект я не могу в силу NDA, но там был весьма навороченный интерфейс пользователя (фактически АСУ ТП с графиками и трендами, кучей настроек и контролов). Аутсорсинг в одной жаркой стране запросил астрономическое количество времени. Для десктопного приложения под Windows существует не так чтобы очень много возможностей наваять интерфейс, но в данном конкретном случае именно к интерфейсу особых претензий не было, и когда я посмотрел, как там всё устроено, то просто воспользовался существующим интерфейсом, по итогу на всю работу ушло совсем немного времени, а вот в освободившееся время меня посетила идея, что таким же образом подобный GUI можно использовать вообще где угодно, там где можно вызывать экспортированные из DLL C функции, так что я расчехлил Раст, который на досуге с удовольствием изучаю...
Зачем это тогда на Хабре, ведь способы снабдить приложение на Расте интерфейсом есть и их довольно много? Во-первых, так ещё никто и никогда не делал, так что тут есть определённая "техническая новизна", во-вторых это весёлое и забавное, местами даже полезное упражнение, а в-третьих хотелось бы в комментариях обсудить данный подход, в нижеизложенном потоке сознания есть определённые "зёрна" смысла, как мне кажется, возможно тут "выкристаллизуется" опенсорсный аналог, ибо нет такой вещи, которую нельзя было бы улучшить.
У читателей предполагаются базовые знания Си и Раста. Поехали!
NI LabWindows/CVI
Изначально приложение было написано на этакой "эзотерической" штуковине NI CVI, и прежде чем мы коснёмся Раста, имеет смысл показать, как в этой среде вообще создаётся интерфейс. Для тех, кто хочет попробовать самостоятельно, существует триальный период, но вот для скачивания, возможно, придётся приложить некоторые усилия, таковы уж современные реалии. Те, кто не хочет заморачиваться, могут прочитать первую часть, программируя "вприглядку", но она важна для понимания, и там всё весьма и весьма несложно. Единственная статья на Хабре, где встречается интерфейс из CVI вот эта — Запускаем Doom на пульте от тепловоза.
Что такое этот CVI? На самом деле это чистый Си компилятор (даже не С++), простой как пять копеек (там под капотом древний clang 3.3 на самом деле), но снабжённый огромным количеством библиотек для оборудования NI (теперь Emerson), приличной мат библиотекой, и в общем среда довольно удобна, хотя и несколько аскетична, никакого интегрированного "ИИ" туда пока не завезли. Нас же в контексте статьи интересует лишь интерфейсная часть, там можно делать GUI приложения.
При старте нас встречает вот такое окно, в котором нам нужен пункт "Проект из темплейта":

В следующем диалоге нам предложат три варианта — консольное приложение, UI или DLL

нам нужно с интерфейсом само собой — User Interface Application. Темплейты можно добавлять свои, а Workspace — это аналог солюшн файла в Студии. Один воркспейс может включать несколько проектов.
Рабочее пространство выглядит вот так, для нас создали три файла — User Interface Application.uir — это собственно интерфейс (примерно как xaml в Шарпе, но бинарный и в проприетарном формате) и собственно код-заготовка с заголовочным файлом впридачу:

Давайте сделаем совсем примитивное приложение — два контрола для значений с плавающей точкой, сложим их вместе да покажем результат в индикаторе. До кучи добавим кнопку выхода. Пока всё. Контролы на интерфейс набрасываются вот так, всё красиво, зацените сколько там всего есть разного:

Ничего не напоминает? Да это же примерно как в Дельфи (ну или WinForms/WPF).
Теперь просто переименуем наши контролы и назначим им функцию обратного вызова (Сallback, с вашего позволения я буду называть "коллбек") OnChange, она будет вызываться когда пользователь будет менять значения:

Как видите, там много чего можно задать, начиная от значений по умолчанию и заканчивая цветами. форматом и точностью, при этом изменения сразу показываются в окошке быстрого редактирования.
Ещё для результата нам не нужны кнопки инкремента и декремента, сделаем этот контрол индикатором:

Вот, теперь можно кодить.
В заголовочном файле, который автогенерированный, мы увидим следующее: пять идентификаторов (панель и четыре элемента) и три коллбека (один наш, а два автогенерённые для панели и кнопки):
#define PANEL 1 /* callback function: panelCB */
#define PANEL_NUMERIC_1 2 /* control type: numeric, callback: OnChange */
#define PANEL_NUMERIC_2 3 /* control type: numeric, callback: OnChange */
#define PANEL_RESULT 4 /* control type: numeric, callback: (none) */
#define PANEL_QUITBUTTON 5 /* control type: command, callback: QuitCallback */
int CVICALLBACK OnChange(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK panelCB(int panel, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK QuitCallback(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
Трогать этот файл нам нельзя, да и не надо, он автоматически обновляется при добавлении/удалении контролов и коллбеков, но он нам понадобится в Расте из-за идентификаторов. Идентификатор всегда нумеруются с 1 и дальше в порядке создания контролов, если мы удалим контрол, то они все изменятся для нижележащих контролов.
Собственно темплейт коллбека генерится вот отсюда:

На этом приготовления заканчиваются, теперь интерфейс лежит в *.uir файле, а в Си файле у нас есть вот такая функция main:
/// HIFN The main entry-point function.
int main (int argc, char *argv[])
{
int error = 0;
/* initialize and load resources */
nullChk (InitCVIRTE (0, argv, 0));
errChk (panelHandle = LoadPanel (0, "User Interface Application.uir", PANEL));
/* display the panel and run the user interface */
errChk (DisplayPanel (panelHandle));
errChk (RunUserInterface ());
Error:
/* clean up */
if (panelHandle > 0)
DiscardPanel (panelHandle);
return 0;
}
И три наших коллбека-заготовки:
//=========================================================================
// UI callback function prototypes
/// HIFN Exit when the user dismisses the panel.
int CVICALLBACK panelCB (int panel, int event, void *callbackData,
int eventData1, int eventData2)
{
if (event == EVENT_CLOSE)
QuitUserInterface (0);
return 0;
}
int CVICALLBACK OnChange (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event) {
case EVENT_COMMIT:
break;
case EVENT_TIMER_TICK:
break;
}
return 0;
}
int CVICALLBACK QuitCallback (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event) {
case EVENT_COMMIT:
break;
case EVENT_TIMER_TICK:
break;
}
return 0;
}
Собственно из кода выше уже понятно как оно работает. При старте приложение инициализирует рантайм через InitCVIRTE(), затем грузит панель "как есть" через LoadPanel (0, "User Interface Application.uir", PANEL)), после чего показывает её на экране DisplayPanel (panelHandle) и встаёт в цикл интерфейса, спрятанный внутри RunUserInterface (), где и находится до тех пор, пока пользователь не завершит приложение, тогда мы зовём DiscardPanel(). Там вполне годная документация.
Каждый раз, когда пользователь будет менять значения в контролах a и b, будет вызываться функция OnChange(). Никакого поллинга интерфейса, никакого цикла рендеринга, всё очень компактно и прозрачно.
Чтобы наполнить приложение функционалом, надо сложить оба контрола и записать результат, это четыре строчки кода, которые надо добавить вот сюда:
int CVICALLBACK OnChange (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event) {
case EVENT_COMMIT:
double a, b;
GetCtrlVal(panelHandle, PANEL_NUMERIC_1, &a);
GetCtrlVal(panelHandle, PANEL_NUMERIC_2, &b);
SetCtrlVal(panelHandle, PANEL_RESULT, a + b);
break;
}
return 0;
}
Мы просто получаем значения двух контролов через GetCtrlVal() и пишем результат при помощи SetCtrlVal(). Заботиться об обновлении интерфейса нам не надо, это берёт на себя рантайм. В теории control возвращает нам ID, от которого прилетело изменение, но это нам тут не нужно. Механизм коллбека детально расписан вот здесь — Using Callback Functions to Respond to User Interface Events.
Ну и напоследок оживим кнопку выхода, вызвав QuitUserInterface (0):
int CVICALLBACK QuitCallback (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event)
{
case EVENT_COMMIT:
QuitUserInterface (0);
break;
case EVENT_TIMER_TICK:
break;
}
return 0;
}
EVENT_TIMER_TICK нам не нужен (но можно добавить таймер если надо, он будет срабатывать с нужным интервалом, а значений event там довольно много, можно перетаскивание оживить или наведение мыши и т.д.).
Вот, собственно и готово, можно запускать:

Технически все функции, используемые выше, вызываются из CVI рантайма. Среда рантайма, кстати, полностью бесплатна в отличие от среды разработки. Давайте сбилдим 64 бит приложение и посмотрим зависимости (можно и статически, но я предпочитаю Process Explorer, так как часть либ могут подгружаться динамически):

Видите, оно зависит лишь от статически линкованной cvirte.dll да динамически подгружаемой mesa.dll, остальные — стандартные библиотеки.
Сама по себе cvirte.dll и её зависимости:

Ну а mesa.dll зависит также от старенькой vcruntime140.dll. Кстати, инсталлировать рантайм CVI совершенно не обязательно, можно просто бросить cvirte.dll, mesa.dll и ещё пару файлов в папку программы, и оно будет прекрасно грузиться из той же директории, что сделает программу полностью портабельной. Размер cvirte.dll — всего шесть с небольшим мегабайт, что совсем немного по нынешним меркам. Само же приложение весит около пятиста килобайт.
Вот теперь можно поупражняться с Растом. Кстати, способ вызова всех функций CVI из стороннего приложения полностью официален и документирован — Using an External Compiler with LabWindows/CVI, так что ничего "ненормально-нелегального" мы делать в общем не будем.
Те же и Раст
Собственно задача сводится к аккуратному вызову всех функций "как из Си" и пробросу коллбеков. Все прототипы функций, упоминавшихся выше, берутся из заголовочного файла userint.h (он по умолчанию лежит в C:\Program Files (x86)\National Instruments\CVI2020\include), Собственно userint.h требует ещё пару заголовочников cvidef.h и cvirte.h, и всё.
То, как вызываются библиотечные функции DLL из Раста — хорошо известно, но поскольку в userint.h их там сотни, то прежде всего имеет смысл приготовить раст файл со всеми биндингами, чтобы не писать всё вручную, хороший программист должен быть в меру ленивый. Также это упражнение будет полезно, чтобы автоматом генерить константы идентификаторов контролов из заголовочного файла, ведь он меняется каждый раз при добавлении новых элементов на панель интерфейса.
Делаем вспомогательный проект и идём в созданную папку:
cargo new cvi_binder
cd cvi_binder
Из зависимостей нам понадобится bindgen:
[package]
name = "cvi_binder"
version = "0.1.0"
edition = "2024"
[dependencies]
[build-dependencies]
bindgen = "0.72.0"
Да, bindgen требует clang, его надо предварительно инсталлировать, либо как winget install LLVM.LLVM либо pacman -S mingw64/mingw-w64-x86_64-clang. Просто аккуратно следуйте инструкции и всё получится.
Теперь нам нужен файл build.rs вот с таким содержимым:
/**************************************************************************/
/* Rust Build File build.rs */
/**************************************************************************/
use bindgen::MacroTypeVariation;
fn main() -> std::io::Result<()> {
println!("cargo:rerun-if-changed=userint.h");
let input_path = "cvi/userint.h";
let bindings_out_path = "src/userint.rs";
let bindings = bindgen::Builder::default()
.header(input_path)
.default_macro_constant_type(MacroTypeVariation::Signed) // to i32
.generate()
.expect("Unable to generate bindings");
bindings
.write_to_file(bindings_out_path)
.expect("Couldn't write bindings!");
let mut contents = std::fs::read_to_string(bindings_out_path)?;
contents = format!("#![allow(dead_code)]
#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
{}", contents);
std::fs::write(bindings_out_path, contents)?;
Ok(())
}
Тут всё, как учит учебник, единственно добавлена опция дефолтных констант .default_macro_constant_type(MacroTypeVariation::Signed), так по умолчанию они беззнаковые, но все функции CVI желают знаковые, Раст строго к этому относится. Также добавлены опции разрешения dead_code да игнорирования регистра функций, иначе раст будет сыпать предупреждениями (их можно и в отдельный файл бросить, но пусть будет так).
Конвертация происходит безболезненно, за исключением типа __int64, что лечится добавлением трёх строчек в userint.h:
#include <stdint.h>
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
Делаем cargo build и получаем в папке src userint.rs.
Если вы заглянете внутрь, то увидите наши требуемые функции, и, кстати, они там есть как для ANSI, так и для Utf8, что не так уж плохо, юникод поддерживается:
...
pub type UIError = ::std::os::raw::c_int;
unsafe extern "C" {
#[doc = " for panels:"]
pub fn LoadPanelAnsi(
parentPanel: ::std::os::raw::c_int,
fileName: *const ::std::os::raw::c_char,
panelResourceId: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
unsafe extern "C" {
pub fn LoadPanelUtf8(
parentPanel: ::std::os::raw::c_int,
fileName: *const ::std::os::raw::c_char,
panelResourceId: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
...
А так там почти семь тысяч строк кода.
Теперь все биндинги в наших руках, можно ваять приложение
cargo new rust_gui
В папку src закинем свежеиспечённый userint.rs, генерить его каждый раз, само собой не надо, а вот что надо генерить, так это rs файл из заголовочного файла проекта, если он меняется. Сам проект CVI бросим в папку cvi и я его переименую просто в "UI", чтобы не таскать длиннющее "User Interface Application.uir". Чтобы файл ресурсов интерфейса не болтался в папке приложения, закинем его в bin. Ну а чтобы не светить постоянными unsafe, сделаем вот так (хорошо бы и это автоматизировать, но нам просто важен принцип, да и копилот такое легко генерит с одного промпта):
#[inline(always)]
pub fn load_panel(reserved: c_int, uir_file: &str, panel: i32) -> Result<c_int, NulError> {
let uir = CString::new(uir_file)?; // fails if string contains '\0'
let rc = unsafe { LoadPanelAnsi(reserved, uir.as_ptr() as *const c_char, panel) };
Ok(rc)
}
Безопаснее код от этого, конечно не станет, я б назвал это "заметание мусора под ковёр".
Код main, запускающий интерфейс, теперь выглядит вот так:
fn main() {
println!("Hello, GUI world!");
let c_argv = build_c_argv();
if !init_runtime(&c_argv) {
eprintln!("Failed to initialize CVIRTE.");
return;
}
let panel_handle = load_panel(0, "bin/ui.uir", PANEL)
.expect("UIR path contained an interior NUL");
display_panel(panel_handle);
run_user_interface(); // <- staying here
discard_panel(panel_handle);
close_cvi_rte(); // required for external compiler
}
Если вы сравните это с Си кодом выше, то всё один-в-один: Init->Load->Display->Run...
Осталась пара штрихов — нам нужен коллбек, где мы будем складывать два числа, вот он:
#[unsafe(no_mangle)]
pub extern "C" fn OnChange(
panel: c_int,
_control: c_int,
event: c_int,
_callback_data: *mut c_void,
_event_data1: c_int,
_event_data2: c_int,
) -> c_int {
if event == EVENT_COMMIT {
println!("Callback triggered");
let a = get_ctrl_val_f64(panel, PANEL_NUMERIC_1);
let b = get_ctrl_val_f64(panel, PANEL_NUMERIC_2);
set_ctrl_val_f64(panel, PANEL_RESULT, a + b);
}
0
}
get_ctrl_val_f64 и set_ctrl_val_f64 — внутри вызывают GetCtrlVal и SetCtrlVal. Дотошный читатель в этом месте непременно спросит: "а как рантайм будет знать, что вызывать нужно именно этот коллбек?". Тут есть маленькая хитрость — в CVI мы должны зайти в меню поддержки внешнего компилятора и создать объектный файл:

Остальное возьмёт на себя линковщик, также надо слинковаться с рантаймом (cvirt.lib и cvisupp.lib), для этого в build.rs мы поместим вот такие строки (какие библиотеки нужны нам любезно напоминают в диалоге выше, я их скопирую в папку lib):
// official ext compiler support
println!("cargo:rustc-link-lib=lib\\cvirt");
println!("cargo:rustc-link-lib=lib\\cvisupp");
println!("cargo:rustc-link-arg=lib\\ui.obj");
println!("cargo:rustc-link-lib=user32");
Вот, собственно и всё, готова наша гуёвинка на Расте, cargo run нам в руки:

Исполняемый файл, кстати, совсем небольшой, 150 килобайт где-то. Ну а поскольку интерфейс хранится отдельно в *.uir файле, то небольшие косметические изменения можно вносить вообще без перекомпиляции приложения.
Усложняем комбинацию
После этих несложных экспериментов я решил попробовать сделать что-то более-менее осмысленное.
По ходу работы мне и коллегам потребовалась небольшая софтинка для рисования одной специфической диаграммы. Там с математической точки зрения нет ничего сложного (оно и в Excel легко делается), есть шесть измеренных параметров рентгеновского детектора, их по специальным таблицам надо перевести в классы, и построить шестиугольную диаграмму-паутинку, которую можно было бы экспортировать в файл, ну и в качестве эксперимента я сделал интерфейс на два языка, различные режимы отображения, файл справки и всё такое, вот это всё на Расте:

Код, кстати, этак процентов на 90 сгенерирован гитхабовским и майкрософтовским копилотами, если майкрософтоскому сказать, что у меня есть биндинги к CVI, то даже он вполне неплохо справляется. Я начал с того, что попросил его нарисовать шестиугольник на канвасе, затем пять вложенных, и далее промптами просил его написать код для рисования диаграммы, сохранения параметров в toml,и так далее, а после чернового наброска просил гитхабовский напрямую из VSCode дописывать код и рефакторить. С точки зрения чистоты кода там много к чему можно придраться, но как проверка концепции — почему бы и нет. По итогу приложение выглядит почти как "родное" десктопное приложение. Менюшки, и локализация — это стандартные фишки CVI. На всё про всё я угрохал выходные.
Да, по поводу локализации "на лету" вот в этом месте:

В CVI есть встроенный инструмент-утилита, выглядит вот так:

Это не идеал, но в принципе позволяет перевести интерфейс на несколько языков, при этом файлы локализации хранятся отдельно, что в теории позволяет добавлять языки без перекомпиляции приложения, там есть API для подгрузки файлов локализации и перевода интерфейса "на лету".
Плюсы и минусы
Как у любого решения, есть "за" и "против", начнём, пожалуй, с нескольких фатальных недостатков.
Во-первых, для того, чтобы создать интерфейс таким образом, нам нужна среда разработки CVI, а это платная штука, сильно платная. На момент написания статьи это либо абонемент на год за 1850, либо пожизненная лицензия за 6470 (и то без обновлений), и отнюдь не рублей. Если до кучи добавить библиотеку обработки изображений (я пример с ней покажу чуть ниже), то это ещё 2290 в год или 8010, а у этой библиотеки машинного зрения к тому же ещё и рантайм платный, это 620 на каждую инсталляцию, и всё это в евро, такой вот кровавый энтерпрайз. Единственно, что бесплатно — "голый" рантайм CVI. Вы видели компилятор Си за шесть с лишним тысяч евро? Теперь видели.
Во-вторых, это всё только и исключительно под Windows.
В-третьих, с технической точки зрения, не поддерживается HiDPI. То есть, если выставить в Windows масштаб больше 100%, скажем на 4К мониторе, то всё будет отчаянно замыленно, а если отключить масштабирование для данного приложения, то само собой, отчаянно мелко. Это дело, конечно можно скомпенсировать, создав индивидуальные *.UIR файлы разного размера (масштаба) под разные разрешения, и подгружать наиболее подходящий, но при более-менее сложном интерфейсе разработка превратится в ад.
Ну а из плюсов — это прежде всего WYSIWYG — что видите, то и получаете, интерфейс создаётся с пиксельной точностью, размещаете как хотите, перетягиваете мышкой, выравниваете, используете свои шрифты и всё такое. Кастомизировать контролы можно, хотя и не настолько, насколько в WPF или Авалонии. Конечно, редактор в Студии значительно превосходит по возможностям, было бы здорово сразу видеть координаты и выравнивание, но тем не менее:

В CVI это тоже есть, но не так красиво:

Есть сложные контролы типа графиков или вьюпорта для изображений, с регионами интереса, зуммированием. Поскольку ноги у этого продукта растут из лабораторий, то с графиками там всё более чем хорошо.
Интерфейс отделён от кода, и собственно самого кода требуется минимальное количество. Идеологию работы на коллбеках тоже запишем в плюс.
В общем при наличии лицензии имеет смысл, а без — практически нет, если только не заморочиться разбором UIR файла и написанием своего редактора. Кстати, UIR файл можно сохранить и загружать в текстовом виде, но его загрузка при более-менее сложном интерфейсе будет происходить очень медленно и печально. Также в принципе все элементы интерфейса можно создавать и динамически, в рантайме, это избавит от необходимости использовать UIR файл вообще, и, следовательно необходимости инсталлировать среду разработки, но это напрочь убьёт удобство интерактивного создания интерфейса и привнесёт кучу рутинной работы, есть и более удобные решения.
GUI Тулкиты для Раста
Существует большое количество тулкитов, я хотел было добавить опрос, кто чем пользуется, но вот нашёл такой сайт, где они почти все перечислены — Are we GUI Yet? Их там на данный момент аж 45 штук, и то не все — есть ещё ratatui для консольных интерфейсов и libcosmic, используемый в Pop!_OS. Но вот, что любопытно, что такого фреймворка, хотя бы отдалённо похожего на тот, что описан выше, я не встречал. В основной массе GUI создаётся в рантайме (как imgui, к примеру), а в статитике почти нет. На реддите я видел эксперимент по вкорячиванию Авалонии в Раст, но там всё вроде как заглохло. Вот есть Slint, но он довольно далёк от идеала, хотя и симпатично сделан. Я пробовал воспользоваться Qt дизайнером, но в смысле интеграции с Растом там просто ад.
"Лакмусовой бумажкой" пригодности фреймвока для меня является небольшое приложение, позволяющее открыть 16 бит картинку, где есть возможность показать её на экране, провести линию и на графике изобразить профиль уровней серого, это стандартная измерительная операция в обработке изображений. Посчитать среднее, минимум, максимум, стандартное отклонение. Ну там ещё гистограмму, само собой, равно как выделить регионы интереса и показать метки, скажем для разметки датасет для машинного обучения. И должна быть возможность сделать такое. пользуясь примитивами "из коробки" и по итогу убить на это очень небольшое количество времени. Как-то вот так, это тоже Раст:

Коллбек, который срабатывает при перемещении линии по экрану:
#[unsafe(no_mangle)]
unsafe extern "C" fn visionWindowCallback(
event: u32,
_window: c_int,
_tool: c_int,
rect: nivision::Rect,
) {
println!("visionWindowCallback Callback");
unsafe {
if event != IMAQ_DRAW_EVENT as u32 {
return;
}
println!("DRAW EVENT");
// Calculate line coordinates
let start = nivision::Point { x: rect.left, y: rect.top };
let end = nivision::Point { x: rect.left + rect.width, y: rect.top + rect.height };
// Generate line profile
let profile = imaqLineProfile(IMAGE as *const Image_struct, start, end);
// Update plot
if PLOT_EXISTS {
DeleteGraphPlot(
PANEL_HANDLE, PANEL_GRAPH,
PLOT_HANDLE, VAL_IMMEDIATE_DRAW,
);
}
PLOT_HANDLE = PlotY(
PANEL_HANDLE, PANEL_GRAPH,
(*profile).profileData as *mut c_void,
(*profile).dataCount as size_t,
VAL_FLOAT, VAL_THIN_LINE, VAL_EMPTY_SQUARE, VAL_SOLID, 1,
VAL_RED,
);
PLOT_EXISTS = true;
// Update statistics display
SetCtrlValAnsi(PANEL_HANDLE, PANEL_MIN, (*profile).min);
SetCtrlValAnsi(PANEL_HANDLE, PANEL_MAX, (*profile).max);
SetCtrlValAnsi(PANEL_HANDLE, PANEL_MEAN, (*profile).mean);
SetCtrlValAnsi(PANEL_HANDLE, PANEL_STD_DEV, (*profile).stdDev);
SetCtrlValAnsi(PANEL_HANDLE, PANEL_COUNT, (*profile).dataCount);
// Cleanup
imaqDispose(profile as *mut _);
}
Но тут уже для imaqLineProfile() потребуется рантайм-лицензия.
Вот, собственно и всё, чем хотелось бы поделиться в этот пятничный вечер.
Код к статье, если кому любопытно:
Пример со сложением двух чисел (в папку exe я сложил библиотеки) — guirust_habr.
Пример приложения для рисования диаграммы — SpiderChart.
Всем добра и хороших выходных!
