0

I am working with this CXX rust example code

https://github.com/dtolnay/cxx/blob/master/demo/src/main.rs

// Toy implementation of an in-memory blobstore.
//
// In reality the implementation of BlobstoreClient could be a large complex C++
// library.
class BlobstoreClient::impl {
  friend BlobstoreClient;
  using Blob = struct {
    std::string data;
    std::set<std::string> tags;
  };
  std::unordered_map<uint64_t, Blob> blobs;
};

BlobstoreClient::BlobstoreClient() : impl(new class BlobstoreClient::impl) {}

but I've not ever done this before... my current (de)constructors look like

TWSApiClient::TWSApiClient():
        m_osSignal(2000)//2-seconds timeout
        , m_pClient(new EClientSocket(this, &m_osSignal))
        , m_state(ST_CONNECT) // starts at m_state = 0
        , m_sleepDeadline(0)
        , m_orderId(0)
        , m_extraAuth(false) {}

TWSApiClient::~TWSApiClient()
{
    if( m_pReader ){
        m_pReader.reset();
    }
    delete m_pClient;
}

When I compiled my Rust code... and try to call a Class function... it's not found.. my guess is I am not implementing this Pointer Implementation style that the CXX code is using.. do I merely move my initiators m_state(ST_CONNECT) into the curly braces of the first code block ?

is this even required for CXX ?

fn main() {

    println!("Starting TWSApiClient connect()");
    let client = ffi::new_twsapi_client();

    client.connect(4002, 333);
    println!("Finished TWSApiClient connect()");

}
error[E0599]: no method named `connect` found for struct `UniquePtr<TWSApiClient>` in the current scope
  --> src/main.rs:22:12
   |
22 |     client.connect(4002, 333);
   |            ^^^^^^^ method not found in `UniquePtr<TWSApiClient>`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `twsapi_grpc_server` due to previous error

EDIT: The full main.rs

#[cxx::bridge(namespace = "com::ourco")]

mod ffi {
    unsafe extern "C++" {
        include!("twsapi_grpc_server/include/twsapi-client.h");
        include!("twsapi_grpc_server/include/AvailableAlgoParams.h");
        include!("twsapi_grpc_server/include/AccountSummaryTags.h");
        include!("twsapi_grpc_server/include/Utils.h");
        type TWSApiClient;
        fn new_twsapi_client() -> UniquePtr<TWSApiClient>;
        #[allow(dead_code)]
        fn connect(self: Pin<&mut TWSApiClient>, port: i32, client_id: i32) -> bool;
    }
}

fn main() {
    println!("Starting TWSApiClient connect()");
    let _client = ffi::new_twsapi_client();
    //_client.connect(4002, 333);
    println!("Finished TWSApiClient connect()");
}
Erik
  • 2,782
  • 3
  • 34
  • 64
  • Where is your definition for `TWSApiClient`? Are you following the structure provided in the demo? Did you forgot to register in the `build.rs` file? – Alex Vergara Nov 15 '22 at 11:06
  • absolutely following the demo.. and I got the demo code working as-is.. the only thing I can see is perhaps my linking is not working? if I comment out the call to `.connect()` multiple linking errors occur.. – Erik Nov 16 '22 at 14:42
  • Can we see the linker errors that you get when you comment out `.connect()`? – DekuDesu Feb 14 '23 at 04:35
  • 1
    Also can we see the entirety of `src/main.rs` – DekuDesu Feb 14 '23 at 04:36
  • Can you really call `connect` directly on the `UniquePtr`? What errors do you get if you do `let mut client = ffi::new_twsapi_client(); client.pin_mut().connect(4002, 333);` ? – Ted Lyngmo Feb 14 '23 at 20:51
  • @DekuDesu added whole main.rs – Erik Feb 15 '23 at 20:58

1 Answers1

1

The cxx documentation says this:

For mutation support, the bridge is required to use Pin<&mut MyType>. This is to safeguard against things like mem::swap-ing the contents of two mutable references, given that Rust doesn't have information about the size of the underlying object and couldn't invoke an appropriate C++ move constructor anyway.

This is why your connect method has to be declared as taking self: Pin<&mut TWSApiClient>.

That's why this fails to compile:

    let client = ffi::new_twsapi_client();
    client.connect(4002, 333);

Calling new_twsapi_client() returns a UniquePtr<TWSApiClient>, but the receiver of connect is a Pin<&mut TWSApiClient>.

UniquePtr does expose an as_mut() method, which returns Option<Pin<&mut T>>. That highlights another issue: a C++ std::unique_ptr can actually contain a null value, so we have to deal with that possibility.

This does compile:

    let client = ffi::new_twsapi_client();
    if let Some(client) = client.as_mut() {
        client.as_mut().connect(4002, 333);
    }

If you know for sure that the pointer is not null then you can use pin_mut(). This method returns a pinned mutable reference, panicing if the UniquePtr contains a null pointer, so this also compiles:

    let client = ffi::new_twsapi_client();
    client.pin_mut().connect(4002, 333);
harmic
  • 28,606
  • 5
  • 67
  • 91
  • The last section looks pretty much like what I asked OP about. _"What errors do you get if you do `let mut client = ffi::new_twsapi_client(); client.pin_mut().connect(4002, 333);` ?"_ but he neglected to comment on that. It'll be interesting to see if your answer gets more attention. – Ted Lyngmo Feb 16 '23 at 19:55
  • I see you Ted, and apologize for being slow to react to the bounty questions.. I have not had time to even test this suggestion but I will have to award the bounty and try later... I have a feeling I will need to make an even bigger bounty and keep an eye out for you Ted to let you answer something .. because I do appreciate the help – Erik Feb 20 '23 at 14:23