How to interact with an object in a binded cpp lib?

Hi,

I have some more problems with binding c++ libs and the Rust ones. Last time @alice (solution) helped me a lot by posting an example which showed me that I really needed to go down to int pointers and length to pass a vector from and to my Rust code. Now I have a more complex problem where I need to construct an object, keep it in my c++ lib and send queries to it which then the object needs to process. To emulate the problem i am posting a small cpp code, src organization and my "attempt" to do it (which does not work since I don't know how to approach in designing a solution )..

Src design:

robj -+- src -+- ffi -+- obj.cpp
      |       |
      |       +-main.rs
      +-build.rs

build.rs

extern crate cc;

fn main() {
    cc::Build::new()
        .file("src/ffi/obj.cpp")
        .compile("libffobj.a");
}

main.rs

extern crate libc;

extern {
    fn Obj(list: *const libc::c_int, len: libc::c_int);  // obviously not working as it needs API
}

fn main() {
    let vec = vec![1, 2, 3, 4, 5, 6];
   // Problem starts here ...
    unsafe { Obj(vec.as_ptr(), vec.len() as libc::c_int) };
 
   // ONCE constructed and alive I would like to make 
   // get() queries : obj.get(3)  -> returns 4 

   // WHAT is the proper approach here ?
}

obj.cpp

#include <iostream>
#include <vector>
using namespace std;


extern "C"{
class Obj {

public:
	Obj(vector<int> vec);
	~Obj();
        int get(int x);
private:
        vector<int> pvec;
};


Obj::Obj(vector<int> vec){
  pvec = vec;
}

Obj::~Obj(){
   vector<int>().swap(pvec);
}


int Obj::get(int x) {
  if (x < (int) pvec.size()){
    return (int) pvec[x];
  }else{
    return (int)  -1;
  }

}
}

So the problem is how to keep cpp object (Obj) alive long enough to be able to make independent queries on it ?

Thank you for any and all help you can provide...

M

I am not sure if you are looking at the problem correctly. Rust does not know how to handle C++ objects like that.

For object constructors, destructors, and public methods, there is a completely separate ABI with an implicit this pointer. Your extern block in Rust does not match that. Even worse, Rust cannot understand the C++ ABI used by the compiler, because every C++ compiler uses its own!

Because of this, all of the examples of C++ interop I know about have C wrappers for the actual method calls, construction, and destruction. For example:

// obj.cpp
class Obj { ...}

extern "C" {
    Obj *create_object(int* items, int len) {
         // ... turn the input args into a vector...
         return new Obj(vec);
    }
    void free_object(Obj *obj) {
        delete obj;
    }
    // wrap all the other methods as well...
}

Those functions can then be called from Rust with an extern block like you had. All of the Obj instances will reside on the C++ heap, and be borrowed by Rust.

I dimly remember a Rust presentation a while ago (I can't find it right now) which talked about how to simplify your C++ class enough so that Rust could actually own an instance of that class safely. It used a more advanced version of the opaque pointer pattern, but as someone who has not touched modern C++, it seemed to me like magic.

Sorry I couldn't be more help. I hope this at least points you in the right direction.

EDIT: clarity, separate two issues

4 Likes

Thank you @jhwgh1968 ! U helped a lot!! I have just a quick follow-up if you could give one more pointer. My rewrite looks like this :blush:

// build.rs  - stays the same 

// src/ffi/obj.cpp

#include <iostream>
#include <vector>
using namespace std;

class Obj {
public:
	Obj(vector<int> vec);
	~Obj();
        int get(int x);
private:
        vector<int> pvec;
};

Obj::Obj(vector<int> vec){
  pvec = vec;
}

Obj::~Obj(){
   vector<int>().swap(pvec);
}

int Obj::get(int x) {
  if (x < (int) pvec.size()){
    return (int) pvec[x];
  }else{
    return (int)  -1;
  }
}

extern "C" {
    Obj* create_object(int* items, int len) {
         vector<int> vec(len);
	    for (int i = 0; i< len; i++){
		 vec[i] = items[i];
	    }
         return new Obj(vec);
    }
    void free_object(Obj *obj) {
        delete obj;
    }

   int get_data (Obj *obj, int x){
	return (*obj).get(x);
   }
}


// main.rs
extern crate libc;
use std::sync::Arc;

extern "C" {
    pub fn create_object(items: *const libc::c_int, len: libc::c_int) -> *mut Obj ;
    pub fn get_data(obj: *mut Obj, x: libc::c_int)-> libc::c_int;
    pub fn delete_object(obj: *mut Obj);
}

struct MyObj {
    raw: *mut Obj,
}

impl MyObj {
    fn new( x: *const libc::c_int, y: libc::c_int) -> MyObj {
        unsafe { MyObj { raw: create_object(x,y) } }
    }
}

impl Drop for MyObj {
    fn drop(&mut self) {
        unsafe {
            delete_object(self.raw);
        }
    }
}


fn main() {
    let vec = vec![1, 2, 3, 4, 5, 6];

    let value = Arc::new(MyObj::new(vec.as_ptr(), vec.len() as libc::c_int));
    let another_value = value.clone();
    println!("Shared counter: {}", Arc::strong_count(&value));
}

so i tried to wrap everything nice and tidy but I am getting an error

error[E0412]: cannot find type `Obj` in this scope
 --> src/main.rs:5:79
  |
5 |     pub fn create_object(items: *const libc::c_int, len: libc::c_int) -> *mut Obj ;
  |                                                                               ^^^ not found in this scope


what am I missing ? (probably something dumb...) Also would this solution work or do you maybe have a better suggestion ?

Thank you !

You need to use void pointers on the Rust side, since the Obj type does not exist in Rust.

1 Like
mod opaque {
    #[repr(C)]
    pub(in super)
    struct Obj {
        _opaque: [*const u8; 0],
    }
}
use opaque::Obj;

is currently the closest Rust equivalent to typedef struct Obj_ Obj in C, so as to manipulate opaque pointers.

1 Like