PowerShell 6.2暴力调用Rust生成的DLL

啊啊,玩的时候就忘了顺便写个markdown文档,现在还得重新理一遍。如有疏漏请多包涵。

缘起

“虽然我也不是很懂,但Rust就是很强。”——我是这么想的。

不过吧,以前玩的时候都是直接执行。要是用Rust做个库,让别人调它会怎样呢?

以前试过用Python 3来调,没有问题。不过现在用PowerShell更多,颇费了一番心力,不过起码搞定了。总之在此把经验记录下来,以便以后抄。

环境

操作系统Win10 1903,PowerShell版本($PSVersionTable.PSVersion.ToString())6.2.2,$PSCulture$PSUICulture均为ja-JP

rustc --versionrustc 1.37.0 (eae3437df 2019-08-13)cargo --versioncargo 1.37.0 (9edd08916 2019-08-02)。现在改用了Visual Studio 2019的link.exe。

参考

实践

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#这边。

主要有三个门槛:

  1. 简单类型对应
  2. 传出struct
  3. 传出字符串

代码已经挺详尽的了,参考资料更详细,就不在这边说太多。主要是我也没弄太懂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
它好使了🥳

主要注意点在于:

  1. Start-Job
  2. $PSScriptRoot
  3. Add-Type -MemberDefinition
  4. [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

InSb

InSb

只是跟工作和生活相关的记录