Rust build for windows

Время на прочтение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.

  1. 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.

    rustup-init.exe installer - step 1

  2. Next, the installer will display current installation options and provide you with the following:
    1) Proceed with installation (default)
    2) Customize installation
    3) Cancel installation

    For our purposes, the 1) (default) will be enough so type 1 and hit ENTER.

    rustup-init.exe installer - step 2

  3. 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.
    rustup-init.exe installer - all steps completed

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:

  1. Go to the Microsoft C++ Build Tools download page and click on the «Download Build Tools» button and run the vs_BuildTools.exe file.
  2. 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«.

  3. 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.
  4. 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.

    Visual Studio Build Tools Installer - Desktop development with C++ workflow

    Click image to enlarge

  5. 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.

  1. Search for «visual studio» or «installer» in the taskbar and launch the Visual Studio Installer.
  2. The «Visual Studio» product should be listed. Click the «Modify» button as shown below.
    Visual Studio Installer - Modify Visual Studio Community

  3. You should see workflows available for download, same as we did with the Microsoft C++ Build Tools earlier.
  4. 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

    Visual Studio Install - Desktop development with C++ workflow

  5. 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

Rust for Windows

Создание проекта

Выберем для проекта какой-нибудь каталог для проекта и перейдем к нему в командной строке с помощью команды cd. Например, в моем
случае для проект будет располагаться в папке C:\rust\windows.

Далее с помощью команды

Создадим в этой папке новый проект с названием «hello».

Rust for Windows - создание проекта Cargo

В папке созданного проекта найдем файл 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:

GUI Приложение Windows 10 на Rust

В итоге в меню Пуск в Windows мы сможем увидеть наше приложение, которое там называется так, как было указано в файле манифеста — Windows 10 App in Rust:

Запустим его и увидим элемент TextBlock с надписью, которую мы определили в коде приложения:

Desktop App for Windows 10 in Rust

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

  1. 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:-

  1. Targeted Platforms
    • Review Targeted Platforms ( Pre )
    • Add Specific Target Platforms
      • Review Target Platforms ( Post )
  2. Project
    • Build Project
  3. Artifacts
    • List Artifacts
    • Inspect Artifacts
  4. Test App
    • Watch
      • Task Manager

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
  1. 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
  1. Component
    • i686-pc-windows-msvc
      • Tasks
        • Downloading component
        • Installing component
      • Size
        • 26.9 MB
        • 8 MiB/s
        • 2s ETA

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

  1. i686-pc-windows-msvc (installed)
  2. 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
  1. target\i686-pc-windows-msvc\release\hello_world.exe
  2. 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
  1. 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
  1. 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
  1. Task Manager
    • Columns
      • Platform:- 32 bit

Tools

  1. Find – Option /v
    • find /? ( Command Help )
      • /V Displays all lines NOT containing the specified string.
    • Skipped files in the deps folder
      • find /V “\deps\”
  2. 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\”

Summary

__,  I am really a son of Rakim.

Everything takes too long around here!

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Как запустить сканирование документа в windows 10
  • Enterprise agreement номер для rds windows server 2019
  • Как выключить брандмауэр windows 10 про
  • Как убрать ассоциацию файлов в windows 10
  • Как получить права администратора в windows 10 без прав администратора офисный комп