cxx
cxx copied to clipboard
how to get a function pointer out of rust::Fn?
this is a follow up to my previous work on getting PcapPlusPlus to work from Rust.
I'd like to stick this thing in its own crate so I can't use a cxx-generated binding for my callback. I think instead I need to pass in the Rust function as a callback. The library is C++ but they use a function pointer for the callback instead of std::function. So I started with my bridge's extern C++ secion like:
unsafe fn read_pcap(fileName: &str,
onMessageReady: unsafe fn(side: i8, tcpData: &TcpStreamData, userCookie: *mut c_void),
userCookie: *mut c_void,
bpfFilter: &str);
which makes the C++ side like:
void read_pcap(rust::Str fileName,
rust::Fn<void(int8_t side, const pcpp::TcpStreamData &tcpData, c_void *userCookie)> onMessageReady,
c_void *userCookie,
rust::Str bpfFilter)
but now I have to make PcapPlusPlus invoke my rust::Fn and it wants a function pointer like:
typedef void(* OnTcpMessageReady) (int8_t side, const TcpStreamData &tcpData, void *userCookie)
what's the best way to get a function pointer out of the rust::Fn?
I found an answer about function pointers and std::function at SO but many answers were either "you can't do that" or "you shouldn't do it". I did use the code from the answer re: fun_ptr_helper and that did work but I'm wary of it. What I did was:
std::function<void(int8_t, const pcpp::TcpStreamData &, c_void *)> onMessageReadyFunction = *onMessageReady;
auto onMessageReadyCallback = get_fn_ptr<0>(onMessageReadyFunction);
pcpp::TcpReassembly tcpReassembly(onMessageReadyCallback, userCookie);
so that works, but is get_fn_ptr legit?
digging into things a little bit more it seems like maybe the private members of Fn could be used to add a public method that returned a function pointer to the underlying bridge function? That sounds a lot better than using that get_fn_ptr code. I'll keep digging.
I think I understand the SO answer now and I see why people don't like it: if it was a library function that UniqueID template parameter can't be made to be unique across libraries including it so it would lead to the wrong function being called sometimes. Also, the static _inst makes it not thread-safe.
I think for my purposes I am going to use something similar since I'm not making that header public and I can control all of the invocations of get_fn_ptr to make sure they're unique. I think making the _inst thread_local is enough to make it thread-safe? I can also get rid of the std::function layer too.
Is there a way that cxx could generate code that would let each instance of rust::Fn be able to return a function pointer? I looked at the code gen and couldn't figure out what to change/add to provide a function pointer to the trampoline without the last argument (fn).
Here's what I'm actually using now but I don't like it a lot:
$ cat include/fn_ptr_helper.hpp
#include "rust/cxx.h"
using rust::Fn;
template <const size_t UniqueId, typename Ret, typename... Args>
struct fn_ptr_helper
{
public:
typedef Fn<Ret(Args...)> fn_type;
static void bind(const fn_type *fn) {
instance().fn_ = fn;
}
static Ret invoke(Args... args) {
return (*instance().fn_)(args...);
}
typedef decltype(&fn_ptr_helper::invoke) pointer_type;
static pointer_type ptr() {
return &invoke;
}
private:
static fn_ptr_helper& instance() {
thread_local fn_ptr_helper inst_;
return inst_;
}
fn_ptr_helper() : fn_(nullptr) {}
const fn_type *fn_;
};
template <const size_t UniqueId, typename Ret, typename... Args>
typename fn_ptr_helper<UniqueId, Ret, Args...>::pointer_type
get_fn_ptr(const Fn<Ret(Args...)> *f) {
fn_ptr_helper<UniqueId, Ret, Args...>::bind(f);
return fn_ptr_helper<UniqueId, Ret, Args...>::ptr();
}
rust::Fn is not the right type for that use case. They correspond to functions with a Rust ABI, so there is a trampoline to translate the ABI between C and Rust; it's not something that the library you are using could call via a single-word C function pointer. That's why you see the SO hack stashing the necessary context for the trampoline entry point into thread_local.
The type you want is extern "C" fn in Rust — something like:
#[cxx::bridge]
mod ffi {
extern "C++" {
include!("...");
type OnMessageReadyCallback = crate::OnMessageReadyCallback;
unsafe fn read_pcap(
fileName: &str,
onMessageReady: OnMessageReadyCallback,
userCookie: *mut c_void,
bpfFilter: &str,
);
}
}
use cxx::{type_id, ExternType};
#[repr(transparent)]
pub struct OnMessageReadyCallback(
pub extern "C" fn(side: i8, tcpData: &TcpStreamData, userCookie: *mut c_void),
);
unsafe impl ExternType for OnMessageReadyCallback {
type Id = type_id!("OnMessageReadyCallback");
type Kind = cxx::kind::Trivial;
}
fn main() {
extern "C" fn callback(side: i8, tcpData: &TcpStreamData, userCookie: *mut c_void) {...}
let callback = OnMessageReadyCallback(callback);
ffi::read_pcap("...", callback, userCookie, "...");
}
#pragma once
#include "..."
using OnMessageReadyCallback = void(*)(int8_t side, const TcpStreamData &tcpData, void *userCookie);
void read_pcap(rust::Str fileName,
OnMessageReadyCallback onMessageReady,
void *userCookie,
rust::Str bpfFilter);