e-tipsmemo

ごった煮

Import dll from Rust dinamically

RustのFFIでdllを使う方法として、

#[link(name = "my_c_library")]
extern "C" {
    fn my_c_function(x: i32) -> bool;
}

extern - Rust
ただ.libが無いと言われてしまった。
windows - Rust linker seeks a LIB, rather than a DLL - Stack Overflow

代わりに、dllを動的にインポートするcrateを使用した。

loading dlls in rust : rust

dlopenがシンプルそうなので、
Windowsでどのように使用するのかテストした。

DLLをテストする(from C++)

公式チュートリアルは.libがある前提の書き方なので、
動的にロードして使う時は、以下を参考にした。
【C++/C#】C++で作成したDLLをC#で呼ぶ - Qiita

#include <iostream>
#include <windows.h>

typedef void(*FUNC1)(const unsigned long long a, const unsigned long long b);
typedef bool(*FUNC2)();
typedef unsigned long long(*FUNC3)();
typedef unsigned(*FUNC4)();

int main()
{

	HMODULE hModule = LoadLibrary(L"testdll.dll");
	if (NULL == hModule)
	{
		std::cout << "Failed to load dll!\n";
		return 1;
	}

	FUNC1 fibonacci_init = (FUNC1)GetProcAddress(hModule, "fibonacci_init");
	FUNC2 fibonacci_next = (FUNC2)GetProcAddress(hModule, "fibonacci_next");
	FUNC3 fibonacci_current = (FUNC3)GetProcAddress(hModule, "fibonacci_current");
	FUNC4 fibonacci_index = (FUNC4)GetProcAddress(hModule, "fibonacci_index");

	std::cout << "Hello World!\n";

	// Initialize a Fibonacci relation sequence.
	fibonacci_init(1, 1);
	// Write out the sequence values until overflow.
	do {
		std::cout << fibonacci_index() << ": " << fibonacci_current() << std::endl;
	} while (fibonacci_next());
	// Report count of values written before overflow.
	std::cout << fibonacci_index() + 1 << " Fibonacci sequence values fit in an " << "unsigned 64-bit integer." << std::endl;
}

GetProcAddressが失敗したときのチェックをした方がよいかもしれないが、とりあえず動いたので良し。

Rust dlopenを使う

dlopenのdocを参考に、
dlopen::symbor - Rust

extern crate dlopen;
#[macro_use]
extern crate dlopen_derive;
extern crate libc;
use dlopen::symbor::{Library, SymBorApi, Symbol};
use libc::{c_char, c_ulonglong};

#[derive(SymBorApi)]
struct FibApi<'a> {
    pub fibonacci_init: Symbol<'a, unsafe extern "C" fn(c_ulonglong, c_ulonglong)>,
    pub fibonacci_next: Symbol<'a, unsafe extern "C" fn() -> c_char>,
    pub fibonacci_current: Symbol<'a, unsafe extern "C" fn() -> c_ulonglong>,
    pub fibonacci_index: Symbol<'a, unsafe extern "C" fn() -> u32>,
}

fn main() {
    let lib = Library::open("testdll.dll").expect("Could not open library");
    let mut api = unsafe { FibApi::load(&lib) }.expect("Could not load Symbols");

    println!("Hello, world!");

    unsafe { (api.fibonacci_init)(1, 1) };

    while {
        let index = unsafe { (api.fibonacci_index)() };
        let current = unsafe { (api.fibonacci_current)() };
        println!("{}:{}", index, current);
        let ret = unsafe { (api.fibonacci_next)() };
        ret != 0
    } {}

    let index = unsafe { (api.fibonacci_index)() };
    println!(
        "{} Fibonacci sequence values fit in an unsigned 64-bit integer.",
        index + 1
    );
}

f:id:katakanan:20210509212102p:plain

DLLが
32bitの時は、i686-pc-windows-msvcで
64bitの時は、x86_64-pc-windows-msvcでビルドする。

関数の引数や、戻り値の型はlibcの定義を使用した方が間違いがないが、boolなど無いものもある。
windowsに使われている型は以下のようになっているらしいので、
データ型の範囲 | Microsoft Docs
もしlibc crateに定義されていない型があっても、これを参照してビット幅などを合わせておけば大丈夫だと思われる。

なので、boolなら1byteなので、とりあえずc_char
unsignedはunsigned intと同じなので、u32


とりあえずDLLをインポートできるということはわかったので、
次は本当に使いたいAPIのDLLが利用できるかを試す。

      • 追記----

rustにもbool型があったのでそれを使えばよかった。
Types · A Guide to Porting C and C++ code to Rust