|
最近 .Net 7 发布之后,因为带了 AOT 编译器,又爆发了一波热度,正好我最近有需求需要使用到这个功能,本文就记录下如何实现将 .Net 7 库编译成静态库,然后用 Rust 链接。
本文实现的是将一个非标准的 DES 算法编译成静态库,供 Rust 调用。该 DES 算法的 C# 实现在这里可以找到:https://github.com/fygroup/Security/blob/master/DES.cs。
本文项目的目录结构为:
./call-net-from-rust-statically
├── des-lib
│ ├── des-lib.csproj
│ └── DES.cs
├── Cargo.toml
├── build.rs
└── src
└── main.rs先创建好 call-net-from-rust-statically 目录:
mkdir call-net-from-rust-staticallyC# 项目部分
首先创建项目:
cd call-net-from-rust-statically
dotnet new classlib -n des-lib将 Class1.cs 重命名为 DES.cs,然后把上面链接中的 DES 类复制到 DES.cs 中,改下命名空间,再加上导出函数的代码,如下:
namespace des_lib;
using System.Runtime.InteropServices;
public class DES
{
[UnmanagedCallersOnly(EntryPoint = "wtf_des_encrypt")]
public static nint FFI_Encrypt(nint message, nint key)
{
var managedMessage = Marshal.PtrToStringUTF8(message);
var managedKey = Marshal.PtrToStringUTF8(key);
if (managedKey == null || managedMessage == null)
{
return nint.Zero;
}
var cipherText = EncryptDES(managedMessage, managedKey);
return Marshal.StringToHGlobalAnsi(cipherText);
}
[UnmanagedCallersOnly(EntryPoint = "wtf_des_decrypt")]
public static nint FFI_Decrypt(nint cipherMessage, nint key)
{
var managedCipherMessage = Marshal.PtrToStringUTF8(cipherMessage);
var managedKey = Marshal.PtrToStringUTF8(key);
if (managedKey == null || managedCipherMessage == null)
{
return nint.Zero;
}
var plainText = DecryptDES(managedCipherMessage, managedKey);
return Marshal.StringToHGlobalAnsi(plainText);
}
[UnmanagedCallersOnly(EntryPoint = "wtf_des_free")]
public static void FFI_FreeMemory(nint buffer)
{
Marshal.FreeHGlobal(buffer);
}
// 将原有 DES 类的内容放在这里。
}其中 wtf_des_encrypt、wtf_des_decrypt 和 wtf_des_free 就是导出的加密、解密以及释放内存的方法。
配置项目的属性:
<Project Sdk=&#34;Microsoft.NET.Sdk&#34;>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<NativeLib>Static</NativeLib>
<PublishAot>true</PublishAot>
<StripSymbols>true</StripSymbols>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>然后就可以用如下命令编译一下试试看:
cd des-lib
dotnet publish -r win-x64 -c Release在构建完毕之后,会在 bin\Release\net7.0\win-x64\publish 目录下生成 des-lib.lib 文件。
Rust 项目部分
在上面的项目构建成功后,将会把 ilcompiler 包缓存,并可以在该目录 %USERPROFILE%/.nuget/packages/runtime.win-x64.microsoft.dotnet.ilcompiler/7.0.1/sdk 找到链接依赖的一些静态库(注意,版本号可能会变更)。
在 call-net-from-rust-statically 目录中创建 Rust 项目:
cd call-net-from-rust-statically
cargo init先添加 windows 依赖,这是因为在链接的时候,.Net 运行时会依赖 Win32 API:
cargo add windows添加 build.rs,一定要注意修改 sdk_path 中的 ilcompiler 版本号(本文讲的是实现步骤,最终的代码我会把 des-lib 的构建也放在 build.rs 中,并从构建的输出中寻找这个版本号,而不需要写死):
use std::path::PathBuf;
fn main() {
let user_profile: PathBuf = std::env::var(&#34;USERPROFILE&#34;).unwrap().into();
let sdk_path: PathBuf = (user_profile)
.join(&#34;.nuget\\packages\\runtime.win-x64.microsoft.dotnet.ilcompiler\\7.0.1\\sdk&#34;);
let manifest_dir: PathBuf = std::env::var(&#34;CARGO_MANIFEST_DIR&#34;).unwrap().into();
let des_lib_path = manifest_dir.join(&#34;des-lib&#34;);
println!(&#34;cargo:rustc-link-arg=/INCLUDE:NativeAOT_StaticInitialization&#34;);
println!(&#34;cargo:rustc-link-search={}&#34;, sdk_path.display());
println!(
&#34;cargo:rustc-link-search={}\\bin\\Release\\net7.0\\win-x64\\publish&#34;,
des_lib_path.display()
);
println!(&#34;cargo:rustc-link-lib=static=windows&#34;);
println!(&#34;cargo:rustc-link-lib=static=bootstrapperdll&#34;);
println!(&#34;cargo:rustc-link-lib=static=Runtime.WorkstationGC&#34;);
println!(&#34;cargo:rustc-link-lib=static=System.Globalization.Native.Aot&#34;);
println!(&#34;cargo:rustc-link-lib=static=des-lib&#34;);
}接下来就是调用了,在 main.rs 中添加:
extern &#34;C&#34; {
fn wtf_des_encrypt(message: *const u8, key: *const u8) -> *const u8;
fn wtf_des_decrypt(cipher_text: *const u8, key: *const u8) -> *const u8;
fn wtf_des_free(ptr: *const u8);
}
fn main() {
let key = b&#34;key\0&#34;;
let cipher_text = unsafe { wtf_des_encrypt(b&#34;message\0&#34;.as_ptr(), key.as_ptr()) };
let cipher_text = unsafe { std::ffi::CStr::from_ptr(cipher_text as *const i8) };
let plain_text = unsafe { wtf_des_decrypt(cipher_text.as_ptr() as _, key.as_ptr()) };
let plain_text = unsafe { std::ffi::CStr::from_ptr(plain_text as *const i8) };
println!(&#34;cipher_text: {}&#34;, cipher_text.to_str().unwrap());
println!(&#34;plain_text: {}&#34;, plain_text.to_str().unwrap());
unsafe {
wtf_des_free(cipher_text.as_ptr() as _);
wtf_des_free(plain_text.as_ptr() as _);
}
}最终版本
仓库地址:https://github.com/hamflx/call-net-from-rust-statically,在本文的基础增加了自动构建 C# 项目,自动查找 ilcompiler 的路径并链接。 |
|