不用安十几 G 的 Visual Studio 了!使用 VC6.0 链接 Rust 程序 您所在的位置:网站首页 编译SDK什么意思 不用安十几 G 的 Visual Studio 了!使用 VC6.0 链接 Rust 程序

不用安十几 G 的 Visual Studio 了!使用 VC6.0 链接 Rust 程序

2023-04-13 09:47| 来源: 网络整理| 查看: 265

其实有点标题党,只是做到了大致能用。

众所周知,Rust 新手有一个经典问题:“我要用的是 Rust,为什么要安装 Visual Studio/MinGW?”,由于这个问题实在被问得太多了,现在 Windows 上安装 GNU ABI 的 Rust 会附带一个 rust-mingw 组件,包含了可用的 GNU 链接器和静态库;而 MSVC ABI 版本的 Rust 在安装时则会引导用户安装 Visual Studio。

我知道的类似的事就是易语言也没有链接器,需要一个 VC 链接器来链接。于是我就想到,能不能用兼具古老和广泛使用的 VC6.0 来链接 Rust 程序。

cargo new hello 建立一个输出 Hello, World! 的新项目 hello,在 hello 文件夹下建立 .cargo/config.toml 文件修改 Cargo 的行为:

[build] target = "i686-pc-windows-msvc"

这里是设置默认 target 为 32 位 MSVC ABI,同时也要用 rustup 安装这个 target:rustup target install i686-pc-windows-msvc 。

在 CMD 下执行 VC6.0 文件夹下的 VC98\Bin\VCVARS32.bat 进入 VC6.0 的命令行开发环境,这样它的 LINK.EXE 就在当前 PATH 里了。执行 cargo build 获得报错:

LINK : fatal error LNK1181: cannot open input file "bcrypt.lib"

Rust 会静态链接 bcrypt.lib,而这个库 Windows Vista 才加,VC6.0 自然不认识。最后支持 VC6.0 的开发 SDK 是 Windows Server 2003 R2,本文假设已安装了此 SDK。

怎么解决这个问题呢?Cargo 并不能自由配置链接器参数。我们可以不用 Cargo,手动管理编译过程,但这样太不方便了。我的解决办法是写一个 Python 脚本用作链接器,将命令行参数处理后再传给 LINK.EXE。

编写 link_wrapper.py:

import sys import subprocess unused_libs = ["legacy_stdio_definitions.lib", "bcrypt.lib"] args = [] for x in sys.argv: if x in unused_libs: continue args.append(x) args[0] = "LINK.EXE" r = subprocess.run(args, capture_output=True) print(r.stdout.decode(errors='replace').replace('\uFFFD', '?')) print(r.stderr.decode(errors='replace').replace('\uFFFD', '?'), file=sys.stderr) sys.exit(r.returncode) # sys.exit(subprocess.run(args, encoding="raw_unicode_escape").returncode)

下面处理字符串是由于 Python 输出字符串会转成 ANSI 编码,这个过程很容易报错,而且 errors="replace" 下产生的 \uFFFD 也就是 � 字符转成 GBK 也会报错……

Cargo 设置链接器只能设置一个可执行文件,所以要把这个脚本用 Pyinstaller 转成可执行文件,结果在 dist 目录下,.cargo/config.toml 改成这样:

[target.i686-pc-windows-msvc] linker = "dist\\link_wrapper.exe" [build] target = "i686-pc-windows-msvc"

再执行 cargo buikd,得到的报错是:

libcore-87bb1d1228d4f647.rlib( ) : error LNK2001: unresolved external symbol ___CxxFrameHandler3 // ... 省略很多 unresolved external symbol ... libstd-4fb70d22a2d15e47.rlib( mbols.o) : error LNK2001: unresolved external symbol __imp__InitOnceComplete@12

都是找不到外部符号,因为 Rust 已经放弃 Windows 7 以下版本 Windows 的支持了,所以会直接使用高版本的系统库函数,VC6.0 的 SDK 里找不到。

这个问题可以通过使用 YY-Thunks 来解决,另有一些符号在 oldnames.lib 里。下载 obj 文件并在 .cargo/config.toml 里配置链接参数:

[target.i686-pc-windows-msvc] linker = "dist\\link_wrapper.exe" rustflags = [ "-C", "link-args=oldnames.lib 3rdparty\\YY_Thunks_for_WinXP.obj", ] [build] target = "i686-pc-windows-msvc"

再编译,会报找不到 ___CxxFrameHandler3 和 _SHSetFolderPathW@16 ,查资料可知,前者是 clang 异常处理相关的,panic="unwind" 时要用到,但即使配置 panic="abort" 仍然会报找不到符号,因为我们安装的 i686-pc-windows-msvc 的标准库里就使用了相应设施;SHSetFolderPathW 则应该是 shell32.lib 里的函数

根据微软的文档,这个函数在 Windows Server 2003 里应该能用,但 SDK 的 shell32.lib 里并没有这个函数。

这样只能手动加上函数定义,我们可以通过 mangle 后的符号名判断两个函数的实际签名。Rust 写符合 C ABI 的函数非常简单:

#![allow(non_snake_case)] use core::ffi::{c_int, c_long, c_ulong, c_void}; type DWORD = c_ulong; type HANDLE = *mut c_void; type HRESULT = c_long; type LPCWSTR = *const u16; // see https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shsetfolderpathw #[no_mangle] extern "stdcall" fn SHSetFolderPathW(_0: c_int, _1: HANDLE, _2: DWORD, _3: LPCWSTR) -> HRESULT { 0 } #[no_mangle] unsafe extern "cdecl" fn __CxxFrameHandler3() { std::process::exit(1); }

再编译,找不到符号的报错没有了,产生了新的报错:

fatal error LNK1103: debugging information corrupt

说明 VC6.0 不识别 rustc 产生的调试信息,因此我们在 .cargo/config.toml 中配置去除调试信息:

[target.i686-pc-windows-msvc] linker = "dist\\link_wrapper.exe" rustflags = [ "-C", "link-args=oldnames.lib 3rdparty\\YY_Thunks_for_WinXP.obj", "-C", "strip=debuginfo", ] [build] target = "i686-pc-windows-msvc"

然后就能成功编译,cargo run 也能正确地输出 Hello, World! 。

除了简单的 HelloWorld,我们还能引入外部 crate,比如经典的猜数字使用了 rand 库;这时要注意,当链接的命令行过长时 rustc 会传递命令文件给链接器,格式是LINK.EXE @path_to\command_file ,前面的 Python 脚本需要适当更改。

完整的项目代码地址在

98 年的链接器今天还能跑,这就是 M$ 的兼容性……



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有