Время на прочтение10 мин
Количество просмотров19K
Наверное не будет уж очень удивительным если я тут, на IT площадке Хабра, скажу что я иногда балую себя программированием.
Основная OS у меня Linux, но иногда приходится собирать исполняемые файлы и для Windows. И естественно что перегружаться в Windows только для сборки exe не особо хочется. С языками C и C++ проблем нет, давно существует кросскомпилятор MinGW, который прекрасно с этим справляется. Про Python и Java даже упоминать не стоит, кроссплатформенность в них изначально. Но в прошлом году я решил попробовать такой пока что новомодный язык, как Rust. При сборке исполняемого файла при помощи включённого в дистрибутив Rust пакетного менеджера cargo вроде как достаточно задать ключ --target, при помощи которого указать результирующий процессор, архитектуру и ABI и при сборке из Linux в результате получить exe, который будет являться стандартным исполняемым файлом для Windows. Но пытаясь так сделать:
cargo build --target x86_64-pc-windows-gnu
я получил только сообщения об ошибках линкера:
error: linking with `gcc` failed: exit code: 1
[...]
= note: /usr/bin/ld: unrecognized option '--nxcompat'
/usr/bin/ld: use the --help option for usage information
collect2: error: ld returned 1 exit status
error: aborting due to previous error
error: could not compile `foobar`.
Если кому интересно как я это поборол и теперь спокойно могу кросскомпилировать программы на Rust для Windows, не покидая Linux, добро пожаловать под кат.
Disclaimer
Далее я рассматриваю только цели 32bit и 64bit pc-windows-gnu, цели pc-windows-msvc для меня интереса не представляют и поэтому в них я не углублялся. Так же речь будет идти о том дистрибутиве Linux, который установлен на моём компьютере, то есть Fedora Linux 31, но я не думаю что на других дистрибутивах Linux будут очень уж существенные различия. И я использую Rust установленный при помощи The Rust toolchain installer, а не входящий в репозиторий Fedora Rust по причине того, что мне иногда требуются nightly сборки Rust, которых в стандартном репозитории, естественно, нет.
Первым делом убеждаемся что у нас установлены необходимые цели, запустив следующую команду:
rustup target list
Получаем список всех возможных целей, и целей, которые у нас установлены:
aarch64-apple-ios
aarch64-fuchsia
[...]
i686-pc-windows-gnu (installed)
[...]
i686-unknown-linux-gnu (installed)
[...]
x86_64-pc-windows-gnu (installed)
x86_64-unknown-linux-gnu (installed)
[...]
Для создания исполняемых файлов для Windows из Linux нам необходимы цели i686-pc-windows-gnu для 32bit exe и x86_64-pc-windows-gnu для 64bit exe. Если данные цели не отмечены как (installed), то доставляем их при помощи команды
rustup target add имя_цели
После убеждаемся что у нас установлен кросскомпилятор MinGW, запустив
rpm -qa | grep mingw
или другой пакетный менеджер для нашего дистрибутива Linux:
mingw32-gcc-9.2.1-1.fc31.x86_64
mingw32-binutils-2.32-6.fc31.x86_64
mingw64-gcc-9.2.1-1.fc31.x86_64
mingw-binutils-generic-2.32-6.fc31.x86_64
mingw-filesystem-base-110-1.fc31.noarch
mingw64-winpthreads-6.0.0-2.fc31.noarch
mingw32-winpthreads-6.0.0-2.fc31.noarch
mingw32-crt-6.0.0-2.fc31.noarch
mingw64-binutils-2.32-6.fc31.x86_64
mingw64-crt-6.0.0-2.fc31.noarch
mingw64-filesystem-110-1.fc31.noarch
mingw32-filesystem-110-1.fc31.noarch
mingw32-cpp-9.2.1-1.fc31.x86_64
mingw64-headers-6.0.0-2.fc31.noarch
mingw32-headers-6.0.0-2.fc31.noarch
mingw64-cpp-9.2.1-1.fc31.x86_64
При отсутствии MinGW устанавливаем необходимые пакеты, запустив
sudo dnf install mingw32-gcc mingw64-gcc
Ну вот вроде бы теперь всё в наличии, далее будем решать проблемы по мере их появления (ага, можно сказать что это получается прям какой-то Test-Driven Development, 
Создаём простейший проект на языке Rust:
[pfemidi@pfemidi rust]$ cargo new foobar
Created binary (application) `foobar` package
[pfemidi@pfemidi rust]$ cat foobar/src/main.rs
fn main() {
println!("Hello, world!");
}
[pfemidi@pfemidi rust]$
Сначала компилируем и запускаем его как родное приложение Linux:
[pfemidi@pfemidi foobar]$ cargo run
Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
Finished dev [unoptimized + debuginfo] target(s) in 1.65s
Running `target/debug/foobar`
Hello, world!
[pfemidi@pfemidi foobar]$
Всё работает. Теперь пробуем его собрать как цель x86_64-pc-windows-gnu:
cargo build --target x86_64-pc-windows-gnu
и получаем всё то же сообщение об ошибке сборки:
error: linking with `gcc` failed: exit code: 1
[...]
= note: /usr/bin/ld: unrecognized option '--nxcompat'
/usr/bin/ld: use the --help option for usage information
collect2: error: ld returned 1 exit status
error: aborting due to previous error
error: could not compile `foobar`.
Понятно, для сборки вызывается не линкер из MinGW, а уже установленный в системе gcc. Исправляем эту ситуацию, для этого создаём в проекте директорию .cargo и в ней файл config со следующим содержимым:
[pfemidi@pfemidi foobar]$ mkdir .cargo
[pfemidi@pfemidi foobar]$ cat > .cargo/config
[target.i686-pc-windows-gnu]
linker = "i686-w64-mingw32-gcc"
ar = "i686-w64-mingw32-ar"
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
ar = "x86_64-w64-mingw32-ar"
[pfemidi@pfemidi foobar]$
Это необходимо для того чтобы при сборке целей для Windows в качестве линкера использовался не установленный в системе gcc, а линкер из MinGW.
Пробуем собрать проект снова:
cargo build --target x86_64-pc-windows-gnu
и получаем другую ошибку от линкера, уже от x86_64-w64-mingw32-gcc:
error: linking with `x86_64-w64-mingw32-gcc` failed: exit code: 1
[...]
= note: /usr/lib/gcc/x86_64-w64-mingw32/9.2.1/../../../../x86_64-w64-mingw32/bin/ld: cannot find -lpthread
collect2: error: ld returned 1 exit status
error: aborting due to previous error
error: could not compile `foobar`.
Дело в том, что Rust по-умолчанию собирает всё в статическом виде, поэтому кроме пакетов mingw32-winpthreads и mingw64-winpthreads, которые dnf автоматически установил как зависимости для mingw32-gcc и mingw64-gcc обязательно должны быть установлены пакеты статических библиотек mingw32-winpthreads-static и mingw64-winpthreads-static, без них линкер всё время будет жаловаться на отсутствующий -lpthread и сборка не пройдёт. Доустанавливаем недостающие пакеты:
sudo dnf install mingw??-winpthreads-static
и опять запускаем компиляцию:
cargo build --target x86_64-pc-windows-gnu
Опять ошибка линковки! Но уже другая:
error: linking with `x86_64-w64-mingw32-gcc` failed: exit code: 1
[...]
= note: /usr/lib/gcc/x86_64-w64-mingw32/9.2.1/../../../../x86_64-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o:crtexe.c:(.rdata$.refptr.__onexitbegin[.refptr.__onexitbegin]+0x0): undefined reference to `__onexitbegin'
/usr/lib/gcc/x86_64-w64-mingw32/9.2.1/../../../../x86_64-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o:crtexe.c:(.rdata$.refptr.__onexitend[.refptr.__onexitend]+0x0): undefined reference to `__onexitend'
collect2: error: ld returned 1 exit status
error: aborting due to previous error
error: could not compile `foobar`.
Линкер жалуется на отсутствующие символы __onexitbegin и __onexitend в файле ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o, который мы установили в составе цели x86_64-pc-windows-gnu. После некоторых раздумий, гугления, чтения доков на сайте Rust, изучения исходников самого Rust, того как и чем сам Rust собирается я понял: дело в том что сам Rust для Windows, и соответственно его компоненты для целей pc-windows-gnu, собраны с использованием MinGW 6.3.0, а у меня в Fedora Linux 31 версия MinGW 9.2.1, поэтому и происходит несоответствие в CRT. Ok, попробуем перенести crt2.o из федориного MinGW в директорию Rust для цели x86_64-pc-windows-gnu. И кроме crt2.o перенесём ещё и dllcrt2.o, который является точкой входа для динамических библиотек:
[pfemidi@pfemidi foobar]$ cd ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/
[pfemidi@pfemidi lib]$ cp /usr/x86_64-w64-mingw32/sys-root/mingw/lib/crt2.o .
[pfemidi@pfemidi lib]$ cp /usr/x86_64-w64-mingw32/sys-root/mingw/lib/dllcrt2.o .
[pfemidi@pfemidi lib]$ cd -
/home/pfemidi/mywork/rust/foobar
[pfemidi@pfemidi foobar]$
и опять запускаем компиляцию нашего проекта на Rust:
pfemidi@pfemidi foobar]$ cargo build --target x86_64-pc-windows-gnu
Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
Finished dev [unoptimized + debuginfo] target(s) in 4.46s
[pfemidi@pfemidi foobar]$
Прекрасно! Всё собралось! Т.к. у меня установлен wine, то тут же я могу и проверить как это работает:
[pfemidi@pfemidi foobar]$ cargo run --target x86_64-pc-windows-gnu
Finished dev [unoptimized + debuginfo] target(s) in 0.38s
Running `target/x86_64-pc-windows-gnu/debug/foobar.exe`
Hello, world!
[pfemidi@pfemidi foobar]$
И даже работает! Теперь пробуем сделать то же самое для 32bit версии исполняемого файла Windows, делаем сразу run без предварительного build:
error: linking with `i686-w64-mingw32-gcc` failed: exit code: 1
[...]
= note: /usr/lib/gcc/i686-w64-mingw32/9.2.1/../../../../i686-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/crt2.o:crtexe.c:(.text+0x75): undefined reference to `__onexitend'
/usr/lib/gcc/i686-w64-mingw32/9.2.1/../../../../i686-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/crt2.o:crtexe.c:(.text+0x7a): undefined reference to `__onexitbegin'
collect2: error: ld returned 1 exit status
error: aborting due to previous error
error: could not compile `foobar`.
Ошибку с отсутствием символов __onexitbegin и __onexitend теперь уже в файле ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/crt2.o мы уже проходили, лечится точно так же, как и для 64bit цели заменой файлов crt2.o и dllcrt2.o на аналогичные по именам, но из дистрибутива MinGW из Fedora:
[pfemidi@pfemidi foobar]$ cd ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/
[pfemidi@pfemidi lib]$ cp /usr/i686-w64-mingw32/sys-root/mingw/lib/crt2.o .
[pfemidi@pfemidi lib]$ cp /usr/i686-w64-mingw32/sys-root/mingw/lib/dllcrt2.o .
[pfemidi@pfemidi lib]$ cd -
/home/pfemidi/mywork/rust/foobar
[pfemidi@pfemidi foobar]$
Проверяем:
[pfemidi@pfemidi foobar]$
[pfemidi@pfemidi foobar]$ cargo run --target i686-pc-windows-gnu
Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
Finished dev [unoptimized + debuginfo] target(s) in 5.12s
Running `target/i686-pc-windows-gnu/debug/foobar.exe`
Hello, world!
[pfemidi@pfemidi foobar]$
Тут теперь тоже всё собирается и работает.
И всё было прекрасно пока я не использовал никакие функции, которые паникуют (macro panic!, функция expect и т.д.) в 32bit целях для Windows. В целях 64bit всё хорошо, а вот в целях 32bit нет.
Добавим в наш проект панику:
[pfemidi@pfemidi foobar]$ cat src/main.rs
fn main() {
println!("Hello, world!");
panic!("I'm panicked!"); // ВОТ НАША ПАНИКА!
}
[pfemidi@pfemidi foobar]
и попробуем собрать как исполняемый файл для 64bit Windows:
[pfemidi@pfemidi foobar]$ cargo run --target x86_64-pc-windows-gnu
Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
Finished dev [unoptimized + debuginfo] target(s) in 2.95s
Running `target/x86_64-pc-windows-gnu/debug/foobar.exe`
Hello, world!
thread 'main' panicked at 'I'm panicked!', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
[pfemidi@pfemidi foobar]$
И компилируется, и собирается, и работает. Попробуем теперь сделать то же самое, но в качестве цели укажем 32bit Windows.
Упс:
[pfemidi@pfemidi foobar]$ cargo run --target i686-pc-windows-gnu
Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
error: linking with `i686-w64-mingw32-gcc` failed: exit code: 1
[...]
= note: /usr/lib/gcc/i686-w64-mingw32/9.2.1/../../../../i686-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/libpanic_unwind-1a1fb2d4d34efaf8.rlib(panic_unwind-1a1fb2d4d34efaf8.panic_unwind.2hbcqjo8-cgu.0.rcgu.o): in function `ZN12panic_unwind3imp5panic17hdaabfe6326236dacE':
/rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libpanic_unwind/gcc.rs:73: undefined reference to `_Unwind_RaiseException'
/usr/lib/gcc/i686-w64-mingw32/9.2.1/../../../../i686-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/libpanic_unwind-1a1fb2d4d34efaf8.rlib(panic_unwind-1a1fb2d4d34efaf8.panic_unwind.2hbcqjo8-cgu.0.rcgu.o): in function `rust_eh_unwind_resume':
/rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libpanic_unwind/gcc.rs:327: undefined reference to `_Unwind_Resume'
collect2: error: ld returned 1 exit status
error: aborting due to previous error
error: could not compile `foobar`.
Опять линкер жалуется на отсутствие символов, но теперь это символы _Unwind_RaiseException и _Unwind_Resume в модуле libpanic стандартной библиотеки Rust.
Снова раздумия, снова гугление, снова чтение доков и изучение исходников как самого Rust, так и его стандартной библиотеки. И я понял почему возникает такая ошибка.
Для разматывания стека при исключении Rust использует метод Dwarf для 32bit целей Windows и SEH для 64bit целей Windows, а MinGW из стандартного репозитория Fedora Linux использует метод SJLJ для 32bit целей Windows и SEH для 64bit целей Windows (о различии между этими методами читать тут). Поэтому 64bit цели собираются без вопросов, а для 32bit просто нет необходимых символов и объектных файлов. Чтобы получить данные файлы необходимо пересобрать MinGW с поддержкой Dwarf вместо поддерки SJLJ по умолчанию для 32bit целей Windows.
Я не буду вдаваться в подробности как именно пересобирать MinGW, это уже не так сложно и не так интересно (configure там надо запускать с параметром --disable-sjlj-exceptions, остальное тривиально), скажу только одно: после того как MinGW пересобран с разматыванием стека Dwarf вместо SJLJ оттуда надо взять всего один файл под названием libgcc_eh.a и положить его в директорию с библиотеками для цели i686-pc-windows-gnu. После этого проекты в которых используются паникующие функции начнут собираться не только для 64bit целей Windows, но и для 32bit:
[pfemidi@pfemidi foobar]$ cd ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/
[pfemidi@pfemidi lib]$ cp ~/rpmbuild/BUILD/gcc-9.2.1-20190827/build_win32/i686-w64-mingw32/libgcc/libgcc_eh.a .
[pfemidi@pfemidi lib]$ cd -
/home/pfemidi/mywork/rust/foobar
[pfemidi@pfemidi foobar]$ cargo run --target i686-pc-windows-gnu
Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
Finished dev [unoptimized + debuginfo] target(s) in 4.57s
Running `target/i686-pc-windows-gnu/debug/foobar.exe`
Hello, world!
thread 'main' panicked at 'I'm panicked!', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
[pfemidi@pfemidi foobar]$
Ну вот, как-то так.
Provide feedback
Saved searches
Use saved searches to filter your results more quickly
Sign up
Rust is a general-purpose, somewhat new language, similar to C++ that compiles to a machine code. In this tutorial, we will first install Rust on the Windows system, then check to see if the installation was successful. Finally, we are going to create a hello world app using both the rustc command and cargo package manager.
Note: This article used Rust 1.62 (July 2022) on Windows 10.
Let’s begin by installing Rust on our Windows machine.
Go to the official Rust download page and look for the «Download rustup-init.exe» button. There should be two of them, one for 32-bit and the other for 64-bit systems. Download the one for your system.
Run the rustup-init.exe installer. The command prompt window should open.
-
The installer will first check if your system has Microsoft C++ build tools and Windows SDK installed. If they are already installed, the installer will skip this step and start at step 2. If on other hand your system is missing them, you will get the message as shown below.
We can install the missing tools after installing Rust, so when asked to Continue? (y/N), press y and hit ENTER.
-
Next, the installer will display current installation options and provide you with the following:
1) Proceed with installation (default)
2) Customize installation
3) Cancel installationFor our purposes, the 1) (default) will be enough so type 1 and hit ENTER.
- The installation will now begin and you will be able to see the components that will be downloaded and installed. When the installation is finished, hit ENTER to close the installer window.
Install the required Visual C++ build tools
As explained earlier at the beginning of the previous section, simply having Rust installed is not enough to compile Rust code. We still require the following components to be installed in our system:
- Microsoft Visual C++ compiler
- Windows SDK
If the installer skipped step 1 in the previous section, your system already contains the required components, so you can jump to the next section.
However, if those components are missing, we get the following error during the compilation of rust code:
error: linker `link.exe` not found
|
= note: program not found
note: the msvc targets depend on the msvc linker but `link.exe` was not found
note: please ensure that VS 2013, VS 2015, VS 2017, VS 2019 or VS 2022 was installed with the Visual C++ option
error: aborting due to previous error
We can install the missing components in two ways. Using either the Visual Studio or the «Microsoft C++ Build Tools» product.
Note: we can have older versions of MSVC (Microsoft Visual C++ compiler) installed for the Rust compiler to work, but in our case, we will install the latest available version.
Let’s first start by looking at how to install these two components, when we don’t have Visual Studio installed in our system. If you already have the Visual Studio installed, skip to the next section.
Using Microsoft C++ Build Tools to install the missing components
We will install the most recent versions of the missing components, which are currently «MSVC — VS 2022 C++ x64/x86 build tools» and «Windows 10 SDK» using the Microsoft C++ Build Tools. The steps are as follows:
- Go to the Microsoft C++ Build Tools download page and click on the «Download Build Tools» button and run the
vs_BuildTools.exefile. - A «Visual Studio Installer» pop-up window should appear with a message that it needs to set up a few things so you can configure your installation. Click on «Continue«.
- This will install the Visual Studio installer. After it finishes, we should be able to see all of the available Workloads that are available for installation.
- We need to install the «Microsoft Visual C++ compiler» and the «Windows SDK» component. We could look for them under the «Components» tab, but the quickest method is to choose the «Desktop Development with C++» workload. This will select the necessary components required for Rust and a few other things. You can see what will be installed in the «Installation details» section on the right side.
Click image to enlarge
- Now click on «Install» button on bottom right corner to install the selected components. When it is finished, you should see the message «Done installing«.
Using Visual Studio to install the missing components
This method is intended for users who already have the Visual Studio IDE installed. The steps for installing the necessary components for Rust compilation are nearly identical to those using the Microsoft C++ Build Tools. We also make use of Visual Studio Installer.
- Search for «visual studio» or «installer» in the taskbar and launch the Visual Studio Installer.
- The «Visual Studio» product should be listed. Click the «Modify» button as shown below.
- You should see workflows available for download, same as we did with the Microsoft C++ Build Tools earlier.
- We need to select the Desktop Development with C++ workload. This should among other things select both the latest MSVC which currently is MSVC — VS 2022 C++ x64/x86 build tools as well as the Windows 10 SDK.
Click image to enlarge
- To install the selected workflow, click on the Install or Modify button.
Testing the Rust installation
To test if the Rust installation was successful, open either the command prompt or PowerShell and run the following command:
rustc --version
This should return the version of the Rust that was just installed.
Creating a simple hello-world app
Let’s now use rustc command to make a simple hello world app. Create a hello.rs file somewhere on your computer with the following code:
fn main() {
println!("Hello, world!");
}
Launch the Command prompt (cmd) or PowerShell, go to the location of the hello.rs file, and run the following command:
rustc hello.rs
If we now check the folder’s content, we should see that the compiler built a hello.exe file. If we run it, it will display a hello world message on the terminal.
Using cargo tool
For simple scripts, using rustc command is fine, but in most cases, we would use the cargo command-line tool.
To create an empty project in the current folder, we use the following command:
cargo init
This command will generate a simple «hello world» app, the same as the one we manually created earlier. The code will be located in src/main.rs file.
To compile the project, we use:
cargo build
This will create a hello-world.exe file in the target/debug folder.
Note: To build for release, use cargo build --release command.
And to run what we built, we give the following command:
cargo run
Note: Before the cargo run runs the project, it will also compile it if needed.
Conclusion
In this article, we went through the steps to install the Rust programming language on the Windows system. The Rust also requires Microsoft C++ build tools and Windows SDK, which we installed using the «Visual Studio Installer» from either the Visual Studio IDE or using the Microsoft C++ build tools tool. Finally, we used the rust compiler rustc command and the cargo tool to create, build and run a simple hello-world Rust application.
Первое приложение
Последнее обновление: 15.06.2021
В этой серии статей мы рассмотрим создание графических приложений на языке Rust для ОС Windows. В настоящее время есть ряд проектов и библиотек, которые позволяют создавать графические приложения. В данном случае мы будем воспользуемся проектом
Rust for Windows, который официально развивается компанией Microsoft и который позволяет использовать любые Windows API
(в том числе Win32 API или Windows 10 API). Официальный репозиторий проекта на github — https://github.com/microsoft/windows-rs.
Установка инструментария
Что нам потребуется для разработки графических приложений для Windows на языке Rust? Естественно у нас должен быть установлен сам компилятор Rust.
Также необходио загузить либо Microsoft C++ Build Tools,
либо Microsoft Visual Studio (например, можно выбрать бесплатный выруск Community).
Вне зависимости от того, какую именно из этих двух программ мы выбрали, при установке нам надо отметить следующие опции:
-
.NET desktop development / Разработка классических приложений .NET
-
Desktop development with C++ / Разработка классических приложений на C++
-
Universal Windows Platform development / Разработка классических приложений для универсальной платформы Windows
Создание проекта
Выберем для проекта какой-нибудь каталог для проекта и перейдем к нему в командной строке с помощью команды cd. Например, в моем
случае для проект будет располагаться в папке C:\rust\windows.
Далее с помощью команды
Создадим в этой папке новый проект с названием «hello».
В папке созданного проекта найдем файл Cargo.toml и откроем его. Добавим в него ссылку на библиотеку (а точнее crate)
windows, которая и представляет проект Rust for Windows:
[package] name = "hello" version = "0.1.0" authors = ["Eugene <metanit22@mail.ru>"] edition = "2018" [dependencies] windows = "0.11.0" [build-dependencies] windows = "0.11.0"
Файл build.rs
Теперь определим в папке проекта новый файл — build.rs. Этот скрипт автоматически вызывается инфраструктурой Cargo непосредственно
перед компиляцией приложения. Определим в этом файле следующее содержимое:
fn main() {
windows::build! {
Windows::ApplicationModel::Activation::*,
Windows::UI::Xaml::Controls::TextBlock,
Windows::UI::Xaml::*,
};
}
Этот скрипт определяет те части Windows API, которые мы собираемся использовать в своем проекте. Они передаются в макрос windows::build!.
Windows имеет множество разных API, но не все они нам нужны.
И тем больше мы включим в проект различных API, тем медленнее будет происходить сборка приложения.
В итоге при выполнении этого файла макрос windows::build! создаст привязки к используемым компонентам Windows API. В частности, для создания и запуска приложения берем функциональность из модуля
Windows::ApplicationModel::Activation и Windows::UI::Xaml. И так как в приложении мы будем использовать метку, которая будет выводить некоторый текст,
то мы также подключаем соответствующий компонент (если точнее структуру) Windows::UI::Xaml::Controls::TextBlock.
И макрос
windows::build!, используя метаданные этих компонентов, сгенерирует соответствующие типы Rust или привязки. Причем даже если мы указываем
только конкретный тип, а не весь модуль, как в случае с Windows::UI::Xaml::Controls::TextBlock, то макрос может включать дополнительные типы, если они необходимы
для функционирования этого типа.
Определение кода приложения
Теперь перейдем к файлу main.rs, который по умолчанию создается cargo в папке src и который по умолчанию
имеет следующий код:
fn main() {
println!("Hello, world!");
}
Изменим этот код на следующий:
#![windows_subsystem = "windows"]
mod bindings {
windows::include_bindings!();
}
use bindings::*;
use windows::*;
use bindings::{
Windows::ApplicationModel::Activation::*,
Windows::UI::Xaml::Controls::TextBlock,
Windows::UI::Xaml::*,
};
#[implement(
extend Windows::UI::Xaml::Application,
override OnLaunched
)]
struct MyApp();
#[allow(non_snake_case)]
impl MyApp {
fn OnLaunched(&self, _: &Option<LaunchActivatedEventArgs>) -> Result<()> {
let window = Window::Current()?;
let textBlock: TextBlock = TextBlock::new()?;
textBlock.SetText("Hello Rust from Metanit.com")?;
textBlock.SetFontSize(22.0)?;
window.SetContent(textBlock)?;
window.Activate()
}
}
fn main() -> Result<()> {
initialize_mta()?;
Application::Start(ApplicationInitializationCallback::new(|_| {
MyApp().new()?;
Ok(())
}))
}
Разбор кода
В начале идет атрибут
#![windows_subsystem = "windows"]
Он указывает на используемую подсистему. Подсистема в свою очередь определяет, как ОС будет выполнять приложение. В реальности для этого атрибута
есть два возможных значения: windows и console. Значение «windows» указывает, что это будет графическое приложение, и позволяет
скрыть консоль. (Без этого атрибута наряду с графическим окном мы увидим консоль приложения)
Далее определяется модуль bindings, который выполняет макрос include_bindings:
mod bindings {
windows::include_bindings!();
}
Этот макрос добавляет сгенерированные привязки в текущий контекст. И через модуль bindings мы сможем обращаться к сгенерированным привязкам.
(Если файлов с кодом много и во все надо добавлять определение данного модуля, то выполнение этого макроса можно вынести в отдельный файл)
Далее идут подключения модулей и их отдельной функциональности:
use bindings::*;
use windows::*;
use bindings::{
Windows::ApplicationModel::Activation::*,
Windows::UI::Xaml::Controls::TextBlock,
Windows::UI::Xaml::*,
};
Создание объекта приложения
Далее нам надо определить функционал приложения. Для представления приложения в библиотеке windows предназначена структура
Windows::UI::Xaml::Application. Однако нам надо определить какой-то свой функционал приложения, чтобы оно имело то поведение и те компоненты,
которые нам нужны. И для этого необходимо изменить поведение данной структуры.
Но стоит учесть, что язык Rust не поддерживает наследование. То есть мы не можем просто так взять и унаследовать
один тип от другого, переопределив его некоторые функции. Однако в библиотеке windows есть атрибут
windows::implement, который позволяет применить для структур Rust функционал классов WinRT или
любую комбинацию существующих интерфейсов COM и WinRT.
И в данном случае мы определяем структуру MyApp, которая и будет представлять приложение.
#[implement(
extend Windows::UI::Xaml::Application,
override OnLaunched
)]
struct MyApp();
Параметр extend атрибута implement указывает, какой тип будет расширять структура.
(В данном случае Windows::UI::Xaml::Application).
А параметр override указывает, какой метод будет переопределять структура. Здесь это метод OnLaunched(), в котором мы можем
задать визуальный интерфейс и логику приложения.
В реальности атрибут implement обернет структуру MyApp в объект COM/WinRT, который расширяет структуру Application, используя агрегацию.
Поскольку мы указали, что структура MyApp будет переопределять метод OnLaunched(), то нам его надо определить для MyApp. Этот метод запускается
при старте приложения:
#[allow(non_snake_case)]
impl MyApp {
fn OnLaunched(&self, _: &Option<LaunchActivatedEventArgs>) -> Result<()> {
let window = Window::Current()?;
let textBlock: TextBlock = TextBlock::new()?;
textBlock.SetText("Hello Rust from Metanit.com")?;
textBlock.SetFontSize(22.0)?;
window.SetContent(textBlock)?;
window.Activate()
}
}
Чтобы указать компилятору, что мы не хотим использовать стиль кода snake_case, применяется атрибут #[allow(non_snake_case)]
В качестве второго параметра метод принимает объект _: &Option<LaunchActivatedEventArgs>, который предоставляет информацию о запуске приложения.
Но поскольку нам этот параметр в данном случае не нужен, то в качестве его имени устанавливаем прочерк _.
В самом методе вначале получаем текущее окно приложения:
let window = Window::Current()?;
Далее создаем объект TextBlock, который представляет текстовую метку, с помощью метода new():
let textBlock: TextBlock = TextBlock::new()?;
С помощью метода SetText() устанавливаем текст метки:
textBlock.SetText("Hello Rust from Metanit.com")?;
А с помощью метода SetFontSize() устанавливаем высоту шрифта
textBlock.SetFontSize(22.0)?;
Далее устанавливаем данный объект TextBlock в качестве содержимого окна и активируем окно:
window.SetContent(textBlock)?; window.Activate()
Функция main
После определения приложения нам надо запустить его в функции main:
fn main() -> Result<()> {
initialize_mta()?;
Application::Start(ApplicationInitializationCallback::new(|_| {
MyApp().new()?;
Ok(())
}))
}
В начале идет вызов функции initialize_mta(), которая инициализирует COM.
Входной точкой в приложение является метод Application.Start(). В качестве параметра он принимает объект
Windows::UI::Xaml::ApplicationInitializationCallback, который инициализирует приложение.
Для создания объекта ApplicationInitializationCallback применяется статическая функция ApplicationInitializationCallback::new(), которая в
качестве параметра принимает объект FnMut(&Option<ApplicationInitializationCallbackParams>) -> Result<()>.
То есть фактически мы можем передать в функцию ApplicationInitializationCallback::new мы можем передаеть функцию, которая принимает один параметр.
В этой функции мы создаем объект структуры MyApp:
MyApp().new()
И после этого окно приложения отобразится на экране компьютера.
Создание файла манифеста
Поскольку мы использует API Windows 10, то просто скомпилировать exe и запустить его одним кликом не получится. Нам нужно определить файл манифеста приложения.
Итак, в папке проекта создадим папку appx. В этой папке определим файл AppxManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
IgnorableNamespaces="uap">
<Identity
Name="Metanit.Hello"
Publisher="CN=Metanit.com"
Version="1.0.0.0" />
<Properties>
<DisplayName>Windows 10 App in Rust</DisplayName>
<PublisherDisplayName>Metanit.com</PublisherDisplayName>
<Logo>StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="en-us" />
</Resources>
<Applications>
<Application Id="App" Executable="hello.exe" EntryPoint="Hello.App">
<uap:VisualElements DisplayName="Windows 10 App in Rust" Description="Description"
Square150x150Logo="Square150x150Logo.png" Square44x44Logo="Square44x44Logo.png" BackgroundColor="transparent">
</uap:VisualElements>
</Application>
</Applications>
</Package>
При локальном развертывании приложения можно оставить все эти данные, как они определены здесь. Но при желании можно и изменить. Вкратце рассмотрим его структуру.
Узел Identity
Задает глобально уникальные идентификаторы приложения и содержит следующие свойства:
-
Name: название пакета приложения. Представляет строку от 3 до 50 алфавитно-цифровых символов, точки и дефиса.
-
Publisher: создатель приложения. Должен начинаться с префикса
"CN=". Может содержать от 1 до 8192 символов. -
Version: версия приложения. Строка в формате
"Major.Minor.Build.Revision"
Узел Properties
Описывает, как информация о приложении будет отображаться пользователю. Содержит следующие параметры:
-
DisplayName: отображаемое имя приложения
-
PublisherDisplayName: отображаемое имя создателя приложения
-
Logo: путь к логотипу приложения
Dependencies
Описывает зависимости приложения. Содержит элемент <TargetDeviceFamily>, который задает диапазон поддерживаемых устройств с помощью параметров:
-
Name: название семейства устройств, которые поддерживают приложение
-
MinVersion: минимальная версия устройства
-
MaxVersionTested: максимальная версия
Узел Resources
Определяет поддерживаемые языки с помощью вложеных элементов <Resource>. Первый элемент <Resource>
задает язык приложения по умолчанию. Должен быть определен как минимум один язык.
Узел Applications
Определяет характеристики для каждого содержащегося в пакете приложения с помощью вложеных элементов <Application>.
Элемент <Application> имеет следующие атрибуты
-
Id: уникальный идентификатор приложения внутри пакета
-
Executable: имя файла приложения (файл exe или dll). Поскольку созданный через cargo проект называется «hello», то по умолчанию
он будет компилироваться в файл «hello.exe» -
EntryPoint: использует свойства Executable и Id
Также с помощью вложенного элемента uap:VisualElements элемент <Application> определяет визуальные
настройки приложения. А именно:
-
DisplayName: отображаемое имя приложения — строка от 1 до 256 символов
-
Description: описание приложения — строка от 1 до 2048 символов
-
BackgroundColor: фоновый цвет в формате трехбайтного шестнадцатеричного значения, предваряемого символом «#», либо название цвета в виде строки.
-
Square150x150Logo: путь к логотипу величиной 150×150
-
Square44x44Logo: путь к логотипу величиной 44×144
Поскольку здесь необходимо как минимум три файла изображения, то можно их загрузить ниже. Файл StoreLogo.png:
Файл Square150x150Logo.png:
Файл Square44x44Logo.png:
Компиляция и установка приложения
Для компиляции и установки приложения в папку проекта добавим новый файл register.cmd со следующим содержимым:
cargo build copy appx\* .\target\debug cd .\target\debug powershell -command "Add-AppxPackage -Register AppxManifest.xml" cd ..\..\
Этот скрипт сначала компилирует приложение командой cargo build. Затем компирует содержимое из папки appx (то есть файл манифеста и изображения)
в папку \target\debug, где будет располагаться скомилированный файл приложения. И затем выполняется команда «Add-AppxPackage»,
которая устанавливает приложения на локальном компьютере.
Таким образом, весь проект будет выглядеть следующим образом:
Теперь, когда все готово, перейдем в командой строке к папке проекта и запустим в ней файл register.cmd:
В итоге в меню Пуск в Windows мы сможем увидеть наше приложение, которое там называется так, как было указано в файле манифеста — Windows 10 App in Rust:
Запустим его и увидим элемент TextBlock с надписью, которую мы определили в коде приложения:
Background
Let us review the steps one will take to build a Rust application that will be ran on a 32-bit MS Windows application.
Lineage
- Rust:- Cargo Build Error – “error: linker `link.exe` not found”
Date Published:- 2023-March-28th
Link
Outline
Here are the steps that we will follow:-
- Targeted Platforms
- Review Targeted Platforms ( Pre )
- Add Specific Target Platforms
- Review Target Platforms ( Post )
- Project
- Build Project
- Artifacts
- List Artifacts
- Inspect Artifacts
- Test App
- Watch
- Task Manager
- Watch
Targeted Platforms
Review Targeted Platforms ( Pre )
Let us list available targeted platforms.
Command:- Rustup target list
Syntax
rustup target list
Sample
Sample – List Target – Windows
rustup target list | findstr /i "windows"
Output
Output – List Target – Windows
>rustup target list | findstr /i "windows" aarch64-pc-windows-msvc i586-pc-windows-msvc i686-pc-windows-gnu i686-pc-windows-msvc x86_64-pc-windows-gnu x86_64-pc-windows-msvc (installed) >
Images – List – Target – Windows
Explanation
Explanation – List – Target – Windows
- A lone MS Windows Platform is targeted
- x86_64-pc-windows-msvc (installed)
Add Targeted Platforms
Let us go over how to target new platforms.
Command:- Rustup target add
Syntax
rustup target add [platform]
Sample
Sample – Add Target – Windows
rustup target add i686-pc-windows-msvc
Output
Output – Add Target – Windows
>rustup target add i686-pc-windows-msvc info: downloading component 'rust-std' for 'i686-pc-windows-msvc' info: installing component 'rust-std' for 'i686-pc-windows-msvc' 26.9 MiB / 26.9 MiB (100 %) 8.9 MiB/s in 2s ETA: 0s
Image – Add Target – Windows
Explanation
Explanation – List – Target – Windows
- Component
- i686-pc-windows-msvc
- Tasks
- Downloading component
- Installing component
- Size
- 26.9 MB
- 8 MiB/s
- 2s ETA
- Tasks
- i686-pc-windows-msvc
Review Targeted Platforms ( Post )
For those who targeted platforms were not currently available, and they needed to add it, please review targeted platforms post installing a new target.
Command:- Rustup target list
Syntax
rustup target list
Sample
Sample – List Target – Windows
rustup target list | findstr /i "windows"
Output
Output – List Target – Windows
>rustup target list | findstr /i "windows" aarch64-pc-windows-msvc i586-pc-windows-msvc i686-pc-windows-gnu i686-pc-windows-msvc (installed) x86_64-pc-windows-gnu x86_64-pc-windows-msvc (installed) >
Images – List – Target – Windows
Explanation
Explanation – List – Target – Windows
We reviewed available targets and filtered on Windows, here are our “installed” matches
- i686-pc-windows-msvc (installed)
- x86_64-pc-windows-msvc (installed)
Build MS Windows 32-bit Project
Command:- Cargo Build
Syntax
cargo build
Sample
Sample – Build – Target – Windows – 32 Bit
cargo build --release --target x86_64-pc-windows-msvc
Output
Output – Build – Target – Windows – 32 Bit – Text
>cargo build --release --target x86_64-pc-windows-msvc Compiling hello_world v0.1.0 (C:\personal\dadeniji\script\rust\hello_world) Finished release [optimized] target(s) in 1.18s
Output – Build – Target – Windows – 32 Bit – Image
Build MS Windows 64-bit Project
Command:- Cargo Build
Syntax
cargo build
Sample
Sample – Build – Target – Windows – 64 Bit
cargo build --release --target x86_64-pc-windows-msvc
Output
Output – Build – Target – Windows – 64 Bit – Text
>cargo build --release --target x86_64-pc-windows-msvc Compiling hello_world v0.1.0 (C:\personal\dadeniji\script\rust\hello_world) Finished release [optimized] target(s) in 0.49s
Output – Build – Target – Windows – 64 Bit – Image
Artifacts
List Artifacts
Dir Command
Syntax
dir *.exe /s
Sample
List Windows Binaries ( *.exe )
dir target\*.exe /s /b
Output
List Windows Binaries ( *.exe )
>dir target\*.exe /s /b | findstr /V "\deps\" C:\personal\dadeniji\script\rust\hello_world\target\i686-pc-windows-msvc\release\hello_world.exe C:\personal\dadeniji\script\rust\hello_world\target\x86_64-pc-windows-msvc\release\hello_world.exe >
Explanation
- target\i686-pc-windows-msvc\release\hello_world.exe
- target\x86_64-pc-windows-msvc\release\hello_world.exe
Inspect Artifacts
Determine Artifact Bitness
Dumpbin.exe
Syntax
dumpbin [file]
Sample – Windows – 32 Bit
Inspect Windows Binaries – 32 Bit
dumpbin /headers target\i686-pc-windows-msvc\release\hello_world.exe | findstr /I "machine"
Output
Text
>dumpbin /headers target\i686-pc-windows-msvc\release\hello_world.exe | findstr /I "machine" 14C machine (x86) 32 bit word machine >
Image
Explanation
- Looked at the executable’s header
- Filtered on machine
- Matched:-
- (x86)
- 32 bit
Sample – Windows – 64 Bit
Inspect Windows Binaries – 64 Bit
dumpbin /headers target\x86_64-pc-windows-msvc\release\hello_world.exe | findstr /I "machine"
Output
Text
>dumpbin /headers target\x86_64-pc-windows-msvc\release\hello_world.exe | findstr /I "machine"
8664 machine (x64)
Image
Explanation
- Looked at the executable’s header
- Filtered on machine
- Matched:-
- 8664 machine (x64)
Test App
Watch
Task Manager
We will run application and watch via Task Manager
Run Application
Syntax
[application-name]
Sample
Sample – Windows – x32
target\i686-pc-windows-msvc\release\hello_world.exe
Output
Image – Windows -x32 – Console
Image – Windows -x32 – Task Manager
Explanation
- Task Manager
- Columns
- Platform:- 32 bit
- Columns
Tools
- Find – Option /v
- find /? ( Command Help )
- /V Displays all lines NOT containing the specified string.
- Skipped files in the deps folder
- find /V “\deps\”
- find /? ( Command Help )
- Findstr – Option /V
- findstr /? ( Command Help )
- /V Prints only lines that do not contain a match.
- Skipped files in the deps folder
- findstr /V “\deps\”
- findstr /? ( Command Help )
Summary
__, I am really a son of Rakim.
Everything takes too long around here!
