啊啊,玩的时候就忘了顺便写个markdown文档,现在还得重新理一遍。如有疏漏请多包涵。
缘起
“虽然我也不是很懂,但Rust就是很强。”——我是这么想的。
不过吧,以前玩的时候都是直接执行。要是用Rust做个库,让别人调它会怎样呢?
以前试过用Python 3来调,没有问题。不过现在用PowerShell更多,颇费了一番心力,不过起码搞定了。总之在此把经验记录下来,以便以后抄。
环境
操作系统Win10 1903,PowerShell版本($PSVersionTable.PSVersion.ToString()
)6.2.2,$PSCulture
和$PSUICulture
均为ja-JP
。
rustc --version
为rustc 1.37.0 (eae3437df 2019-08-13)
,cargo --version
为cargo 1.37.0 (9edd08916 2019-08-02)
。现在改用了Visual Studio 2019的link.exe。
参考
- Calling Rust from C#
- Can I load a DLL written in Delphi into PowerShell?
- Low-Level Windows API Access From PowerShell
实践
Rust包作成
先建立一个package。由于直接cargo new cs_call_rst
默认建立的是binary的包,所以要追加--lib
来建立库。
Set-Location D:\proj
cargo new cs_call_rst --lib
这样就建好了cs_call_rst
文件夹。文件结构不多提,可以看一下这边:Creating a New Package
编辑Cargo.toml
文件:
[package]
name = "cs_call_rst"
version = "0.1.0"
authors = ["InSb"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "our_rust"
crate-type = ["dylib"]
[dependencies]
这样的话,之后生成的库名字叫做our_rust
,而且得是动态链接库。
编辑.\src\lib.rs
:
// reference:
// https://dev.to/living_syn/calling-rust-from-c-6hk
// external crates
use std::os::raw::c_char;
use std::ffi::CString;
static mut STRING_POINTER: *mut c_char = 0 as *mut c_char;
#[repr(C)]
pub struct SampleStruct {
pub field_one: i16,
pub field_two: i32,
pub string_field: *mut c_char,
}
fn store_string_on_heap(string_to_store: &'static str) -> *mut c_char {
// create a new raw pointer
let pntr = CString::new(string_to_store).unwrap().into_raw();
// store it in our static variable (REQUIRES UNSAFE)
unsafe {
STRING_POINTER = pntr;
}
return pntr;
}
#[no_mangle]
pub extern fn free_string() {
unsafe {
let _ = CString::from_raw(STRING_POINTER);
STRING_POINTER = 0 as *mut c_char;
}
}
#[no_mangle]
pub extern fn get_sample_struct() -> SampleStruct {
let test_string: &'static str = "它好使了🥳";
SampleStruct {
field_one: 1,
field_two: 2,
string_field: store_string_on_heap(test_string),
}
}
照别人抄的哦。我也不是很懂。详情见Calling Rust from C#这边。
主要有三个门槛:
- 简单类型对应
- 传出struct
- 传出字符串
代码已经挺详尽的了,参考资料更详细,就不在这边说太多。主要是我也没弄太懂w
再就是,话说free_string()
没用上啊……
编译:
cargo build
我们想要的东西是.\target\debug\our_rust.dll
。这边暂且告一段落。
PowerShell脚本作成
找个地方建个脚本:
# reference:
# https://stackoverflow.com/questions/47224281/can-i-load-a-dll-written-in-delphi-into-powershell
# http://www.fuzzysecurity.com/tutorials/24.html
$job = Start-Job -ScriptBlock {
Param($filePath)
$signature = @"
[StructLayout(LayoutKind.Sequential)]
public struct SampleStruct {
public Int16 field_one;
public Int32 field_two;
public IntPtr string_field;
}
[DllImport(@"$filePath")]
public static extern SampleStruct get_sample_struct();
"@
Add-Type `
-MemberDefinition $signature `
-Name 'StructPoc' `
-Namespace 'SampleUtils' `
-PassThru |
Out-Null
$sample_struct = [SampleUtils.StructPoc]::get_sample_struct()
Write-Output $sample_struct.field_one
Write-Output $sample_struct.field_two
[string]$t = [System.Runtime.InteropServices.Marshal]::PtrToStringUTF8($sample_struct.string_field)
Write-Output $t
} -ArgumentList ($PSScriptRoot + "\target\debug\our_rust.dll")
Wait-Job $job
Write-Output "`n==RESULT=="
Receive-Job $job
执行后输出如下:
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
1 Job1 BackgroundJob Completed True localhost …
==RESULT==
1
2
它好使了🥳
主要注意点在于:
Start-Job
$PSScriptRoot
Add-Type -MemberDefinition
[Runtime.InteropServices.Marshal]::PtrToStringUTF8
Start-Job
这系列的东西,在频繁测试时非常必要。如果没有它包着,执行过脚本之后,那个dll会被占着,Rust那边就没法编译过去,就出了Non-UTF-8 output: LINK : fatal error LNK1104
这样的错误。不用Job的话其实也有可能,比如说在终端里再开一个PowerShell,用完了就exit,再用再开。
-ArgumentList
这个可以接一个参数列表,这里是把dll的路径放进去。$PSScriptRoot
表示当前执行脚本的目录(不含脚本文件名),但是它放到job里就没作用了,只好从job外面传进去。
双引号的here-string可以放变量进去的。当然要是不放心,用单引号here-string再拼接字符串也没关系。
要注意的是,这边直接用Add-Type
并不行,必须用它的暴力加强版。上边那个说Delphi的其实很有用。
要说有没有其他办法,并没想继续试,就没管太多了。
[Runtime.InteropServices.Marshal]::PtrToStringUTF8
,这个在PowerShell 5.x其实并没有,虽说有PtrToStringUni
可以用,但这就是编码的话题了,Rust那边的源码也得改一下。暂且先放。
另外,发现了点问题:有时那个“🥳”不能正常输出。这问题恐怕要多调查一阵子咯。测试时用的是Visual Studio Code自带的终端,总是有点bug。可以用别的终端试试看,比如(不算很稳的)Hyper之类。
后记
说实在的,反正dotNET那边确实提供这样的东西,用就完事儿了。这样的话,可能性就相当多了。
或许以后可以玩玩窗口?,www