Reputation: 11
I have some Cython code to interact with a 3rd party C++ driver library for a USB device. I am able to call the library from Python and open a device session. However, when I try to call the library from Rust using the FFI, I run into issues. Unfortunately, the 3rd party library is closed-source, so the best I can do is make sure my implementation is identical in Python and Rust.
# main.py
from driver import Driver
def main():
print(f"DriverVersion: {Driver.get_driver_version().decode()}")
d = Driver()
success = d.initialize_session()
d.close_session()
assert success
if __name__ == "__main__":
main()
# driver.pxd
from libcpp cimport bool
cdef extern from "NrtControl.h":
bool NrtOpenDevice( const char *cpPort, int *piSession )
bool NrtCloseDevice( int iSession )
const char * NrtGetDriverVersion()
# driver.pyx
cdef class Driver:
cdef int iSession
cdef char *cpComport
def __init__(self):
self.iSession = 0
def initialize_session(self):
return NrtOpenDevice("COM8", &self.iSession)
@staticmethod
def get_driver_version():
return NrtGetDriverVersion()
def close_session(self):
NrtCloseDevice(self.iSession)
# setup.py
from distutils.core import Extension, setup
import numpy as np
from Cython.Build import cythonize
from Cython.Distutils import build_ext
def build():
equipment_exts = [
Extension(
name="driver",
sources=["driver.pyx"],
include_dirs=[
"C:\\Program Files\\NrtControl_for_Windows_20180921\\include",
],
library_dirs=[
"C:\\Program Files\\NrtControl_for_Windows_20180921\\VS2017\\x64\\Release"
],
libraries=["NrtControl"],
language="c++",
)
]
setup(
cmdclass={"build_ext": build_ext},
ext_modules=cythonize(
equipment_exts, compiler_directives={"language_level": 3}
),
)
if __name__ == "__main__":
build()
I run the above with python setup.py build_ext --inplace
and then python .\main.py
and get the following output:
DriverVersion: 1.5.5.0
// Cargo.toml
[package]
name = "nrt-ffi-bindings"
version = "0.1.0"
links = "NrtControl"
build = "build.rs"
edition = "2021"
[dependencies]
libc = "0.2"
// build.rs
fn main() {
println!("cargo:rustc-link-search=C:\\Program Files\\NrtControl_for_Windows_20180921\\VS2017\\x64\\Release");
}
// lib.rs
use libc::{c_char, c_int};
#[link(name = "NrtControl")]
extern "C" {
#[link_name = "?NrtOpenDevice@@YA_NPEBDPEAH@Z"]
pub fn NrtOpenDevice(cpPort: *const c_char, piSession: *mut c_int) -> bool;
#[link_name = "?NrtCloseDevice@@YA_NH@Z"]
pub fn NrtCloseDevice(iSession: c_int) -> bool;
#[link_name = "?NrtGetDriverVersion@@YAPEBDXZ"]
pub fn NrtGetDriverVersion() -> *const c_char;
}
#[cfg(test)]
mod tests {
use std::ffi::{CStr, CString};
use super::*;
#[test]
fn open_close() {
unsafe {
println!(
"DriverVersion: {}",
CStr::from_ptr(NrtGetDriverVersion()).to_str().unwrap()
);
let port = CString::new("COM8").unwrap();
let session: c_int = 0;
let success = NrtOpenDevice(port.as_ptr(), session as *mut c_int);
NrtCloseDevice(session);
assert!(success);
}
}
}
When I run the above with cargo test -- --test-threads=1
, I get the following:
DriverVersion: 1.5.5.0
thread 'tests::open_close' panicked at 'assertion failed: success', src\lib.rs:32:13
C:\\Program Files\\NrtControl_for_Windows_20180921\\VS2017\\x64\\Release
contains
NrtControl.dll
and NrtControl.lib
, which were provided to me by a 3rd party.
Without the link_name
attributes above, I get errors at runtime about failing to resolve external symbols. I had to run dumbin.exe
on NrtControl.lib
and search for the proper symbol names to copy and paste. I tried to find a way to do this with bindgen
, but eventually resorted to this manual process. If anyone knows how I can avoid this, please do share.
So the problem is that my Rust invocation of the NrtOpenDevice
function above returns false
indicating failure, and I'm out of ideas on how to troubleshoot this.
Adding NrtControl.h and nrtdef.h
Following @rodrigo's suggestion below, I changed the call to:
let mut session: c_int = 0;
let success = NrtOpenDevice(port.as_ptr(), &mut session);
and it works (returns true)!
I still need to figure out how to make the link_name attributes target the correct symbol names automatically, as entering them manually is going to get tedious in the future...
Upvotes: 0
Views: 353