😊

RustからC/C++のDLLを呼び出してDLLにアタッチ

2024/07/08に公開

はじめに

RustからVisual Studio 2022で作成したDLLを呼び出す設定と、Visual Studio 2022からC/C++のDLLにアタッチしてデバッグするまでの手順を記載します。

記事を書こうと思ったのは、デバッグしてみたら思わぬ現象(良い方)に出くわしたのがきっかけです。

Windows 11

  • Rust
    • Visual Studio Code(VSCode)
  • C++
    • Visual Studio Community 2022 (以下VS2022)

ソース

https://github.com/the-de/win_rust_cdll/tree/main

プロジェクト作成/実装

1つのフォルダにC++とRustのプロジェクトをそれぞれ作成していきます。

  • root
    • C++ (ソリューション名:sample_dll)
    • Rust (パッケージ名:rust_c)

C/C++ VS2022

  1. VS2022でダイナミックリンクライブラリ(DLL) C++ Windows ライブラリ
    を作成

  2. 最初から用意されているファイル(pch.h/pch.c)にexport関数を定義/実装

pch.h

#include "stdint.h"
extern  "C" {
	__declspec(dllexport) uint32_t __stdcall add(uint32_t, uint32_t);
}

pch.c

uint32_t add(uint32_t a, uint32_t b) {
	return a + b;
}
  1. ビルド
    いったん完成です。

Rust VSCode

  1. cargo new
> cargo new rust_c
  1. VSCodeで main.rs 追記
fn main() {
    let get_u32 = ||{
        let mut a = String::new();
        std::io::stdin().read_line(&mut a).unwrap();
        a.trim().parse::<u32>().unwrap_or_default()
    };

    loop {
        let a = get_u32();
        let b = get_u32();
        let ans = unsafe { add(a,b) };
        println!("{}",ans);
        if ans == 100{
            break;
        }
    }
}

extern "C" { 
    fn add(a:u32,b:u32)->u32; 
}
  1. *.libのリンクと場所を指示

この状態でビルドすると

error LNK2019: 未解決の外部シンボル add

となるのでbuild.rsで*.libのリンク方法と場所を設定します。
リンク方法はダイナミックリンクにします。

use std::env;

fn main(){
    let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    println!(r"cargo:rustc-link-search=native={}/../sampl_dll/x64/Debug",dir);
    println!("cargo:rustc-link-lib=dylib={}","sampl_dll");
}

これでビルドが通ります。

動作確認

VSCodeでfn main()のRunを実行すると

error: process didn't exit successfully: `target\debug\rust_c.exe` (exit code: 0xc0000135, STATUS_DLL_NOT_FOUND)

STATUS_DLL_NOT_FOUNDのとおり、dllが見つけられずに実行できません。
コピーしてくれば実行できるが、今回はVS2022の出力ディレクトリを変更することにします。

ということで、VS2022で出力ディレクトリを変更して再ビルドします。

プロジェクトのプロパティ > 全般 > 出力ディレクトリ

変更前

$(SolutionDir)$(Platform)\$(Configuration)\

修正後

$(SolutionDir)..\rust_c\target\$(Configuration)\

build.rsも修正します。

use std::env;

fn main(){
    let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let deb_rel = env::var("PROFILE").unwrap();    
    println!(r"cargo:rustc-link-search=native={}/target/{}",dir,deb_rel);
    println!("cargo:rustc-link-lib=dylib={}","sampl_dll");
}

アタッチ

  1. VSCodeからfn main()のRunを実行

DebugだとVSCodeがアタッチしているためVS2022がアタッチできないです
実行直後は標準入力待ちのため停止しているので、この隙にアタッチします。

  1. VS2022でアタッチ/ブレークポイント設定

デバッグ > プロセスにアタッチ

アタッチする実行ファイルはRustのパッケージ名(今回はrust_c)で検索してください。
アタッチに成功するとブレークポイントが設定可能になります。
以下のように設定します。

3.Rustのプログラムを進める
数字を2つ入力し、プログラムを進めると設定したブレークポイントで停止します。

あとがき

ここからブレークポイントを進めると、VS2022のままRustコードに入ることができます。
これがVSCode LLDB拡張よりも見やすくて感動しました。

同時にVSCode以外のIDEをきちんと試すべきだと思いました。
RustRoverとかを触っておけばよかったと後悔しています。😭

Discussion