How do you work with a C++ function that returns a shared_ptr when calling it from Rust over FFI?
C++
shared_ptr<Foo> create_foo();
Rust
extern "C" {
pub fn create_foo() -> ???;
}
Bindgen turns a shared_ptr
into an opaque blob.
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to Foo
and might call its deconstructor.
c++ rust shared-ptr ffi
add a comment |
C++
shared_ptr<Foo> create_foo();
Rust
extern "C" {
pub fn create_foo() -> ???;
}
Bindgen turns a shared_ptr
into an opaque blob.
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to Foo
and might call its deconstructor.
c++ rust shared-ptr ffi
Is it at all possible for you to design your code in such a way that either C++ or Rust owns the object and manages its lifetime? You could trigger a callback when it is destroyed, to tell the "other" language to stop holding references to it.
– John Zwinck
Nov 14 '18 at 11:32
You can also return anew shared_ptr<Foo>(...)
... a bit like the doubleBox
trick that is done sometimes from the Rust side.
– rodrigo
Nov 14 '18 at 12:06
Just to clarify this is an existing c++ library and I can't control how the pointer is created. I just get given a shared_ptr<Foo> from a function.
– Tom
Nov 14 '18 at 12:12
So you want to be able to access to pointer inner type ?
– Stargateur
Nov 14 '18 at 13:15
@Stargateur yep but I don't want it to be free'd while I'm using it and if I just use the raw pointer I can't guarantee that.
– Tom
Nov 14 '18 at 13:19
add a comment |
C++
shared_ptr<Foo> create_foo();
Rust
extern "C" {
pub fn create_foo() -> ???;
}
Bindgen turns a shared_ptr
into an opaque blob.
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to Foo
and might call its deconstructor.
c++ rust shared-ptr ffi
C++
shared_ptr<Foo> create_foo();
Rust
extern "C" {
pub fn create_foo() -> ???;
}
Bindgen turns a shared_ptr
into an opaque blob.
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to Foo
and might call its deconstructor.
c++ rust shared-ptr ffi
c++ rust shared-ptr ffi
edited Nov 15 '18 at 15:49
Shepmaster
153k14299436
153k14299436
asked Nov 14 '18 at 11:25
TomTom
212
212
Is it at all possible for you to design your code in such a way that either C++ or Rust owns the object and manages its lifetime? You could trigger a callback when it is destroyed, to tell the "other" language to stop holding references to it.
– John Zwinck
Nov 14 '18 at 11:32
You can also return anew shared_ptr<Foo>(...)
... a bit like the doubleBox
trick that is done sometimes from the Rust side.
– rodrigo
Nov 14 '18 at 12:06
Just to clarify this is an existing c++ library and I can't control how the pointer is created. I just get given a shared_ptr<Foo> from a function.
– Tom
Nov 14 '18 at 12:12
So you want to be able to access to pointer inner type ?
– Stargateur
Nov 14 '18 at 13:15
@Stargateur yep but I don't want it to be free'd while I'm using it and if I just use the raw pointer I can't guarantee that.
– Tom
Nov 14 '18 at 13:19
add a comment |
Is it at all possible for you to design your code in such a way that either C++ or Rust owns the object and manages its lifetime? You could trigger a callback when it is destroyed, to tell the "other" language to stop holding references to it.
– John Zwinck
Nov 14 '18 at 11:32
You can also return anew shared_ptr<Foo>(...)
... a bit like the doubleBox
trick that is done sometimes from the Rust side.
– rodrigo
Nov 14 '18 at 12:06
Just to clarify this is an existing c++ library and I can't control how the pointer is created. I just get given a shared_ptr<Foo> from a function.
– Tom
Nov 14 '18 at 12:12
So you want to be able to access to pointer inner type ?
– Stargateur
Nov 14 '18 at 13:15
@Stargateur yep but I don't want it to be free'd while I'm using it and if I just use the raw pointer I can't guarantee that.
– Tom
Nov 14 '18 at 13:19
Is it at all possible for you to design your code in such a way that either C++ or Rust owns the object and manages its lifetime? You could trigger a callback when it is destroyed, to tell the "other" language to stop holding references to it.
– John Zwinck
Nov 14 '18 at 11:32
Is it at all possible for you to design your code in such a way that either C++ or Rust owns the object and manages its lifetime? You could trigger a callback when it is destroyed, to tell the "other" language to stop holding references to it.
– John Zwinck
Nov 14 '18 at 11:32
You can also return a
new shared_ptr<Foo>(...)
... a bit like the double Box
trick that is done sometimes from the Rust side.– rodrigo
Nov 14 '18 at 12:06
You can also return a
new shared_ptr<Foo>(...)
... a bit like the double Box
trick that is done sometimes from the Rust side.– rodrigo
Nov 14 '18 at 12:06
Just to clarify this is an existing c++ library and I can't control how the pointer is created. I just get given a shared_ptr<Foo> from a function.
– Tom
Nov 14 '18 at 12:12
Just to clarify this is an existing c++ library and I can't control how the pointer is created. I just get given a shared_ptr<Foo> from a function.
– Tom
Nov 14 '18 at 12:12
So you want to be able to access to pointer inner type ?
– Stargateur
Nov 14 '18 at 13:15
So you want to be able to access to pointer inner type ?
– Stargateur
Nov 14 '18 at 13:15
@Stargateur yep but I don't want it to be free'd while I'm using it and if I just use the raw pointer I can't guarantee that.
– Tom
Nov 14 '18 at 13:19
@Stargateur yep but I don't want it to be free'd while I'm using it and if I just use the raw pointer I can't guarantee that.
– Tom
Nov 14 '18 at 13:19
add a comment |
3 Answers
3
active
oldest
votes
std::shared_ptr
is a C++ class and a non-trivial type that can not be exported as is from a library — you need its definition in your target language to conform to the one in C++. To use FFI you need to provide your library functions with a simple C ABI (the C++ ABI is not stable and may change between compiler versions (as might Rust's ABI)) and I doubt that all of functions related to std::shared_ptr
are such, so there is one more obstacle for that.
I'd suggest to return a raw C-pointer from your library instead and own it in Rust.
Even in C++, to load a C++ library you provide C-ABI functions (via extern C
) to gain access to a pointer of your type and then you use it in C++ as how as you want.
So, a few points:
Return a raw C pointer from a function without name mangling so that we know its name and can link to it:
extern "C" Foo* create_foo();
Add a deleter which knows how to properly deallocate the object:
extern "C" void delete_foo(Foo *);
Let the library user (Rust) decide how to own it, for example, by
boxing
the value and using it with atomic reference counter viastd::sync::Arc
(asstd::shared_ptr
does):
extern "C" {
fn create_foo() -> *mut Foo;
fn delete_foo(p: *mut Foo);
}
struct MyFoo {
raw: *mut Foo,
}
impl MyFoo {
fn new() -> MyFoo {
unsafe { MyFoo { raw: create_foo() } }
}
}
impl Drop for MyFoo {
fn drop(&mut self) {
unsafe {
delete_foo(self.raw);
}
}
}
fn main() {
use std::sync::Arc;
let value = Arc::new(MyFoo::new());
let another_value = value.clone();
println!("Shared counter: {}", Arc::strong_count(&value));
}
Let the C++ side forget about owning this pointer - you can't rely on it if it is used from outside the library and you give a raw pointer to it.
If you don't have any access to the library sources, can't do anything with it: the std::shared_ptr
object will not release the pointer ever and we can't make it not to delete the pointer.
3
"C++ abi is not stable" -> "Is not specified"
– hellow
Nov 14 '18 at 11:42
That means it has not been stabilized I guess :)
– Victor Polevoy
Nov 14 '18 at 11:43
It's a difference, because is has not been stabilized means (IMHO), that is about to stabilize and there is a standard for it. Not specified means there no specification at all.
– hellow
Nov 14 '18 at 11:44
Thanks for the suggestion. If I return a raw pointer how do I make sure c++ doesn't free the pointer?Foo * create_foo() { shared_ptr<Foo> smart_p = lib::create_foo(); return smart_p.get(); }
Here smart_p's desctructor will be called at the end of the function and if its the last one it will free the pointer
– Tom
Nov 14 '18 at 11:50
2
TL;DR: How do you work with a C++ shared_ptr<T> in FFI? — you don't.
– Shepmaster
Nov 15 '18 at 15:53
|
show 11 more comments
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to
Foo
and might calls it's deconstructor.
Yes and no. With your actual example. C++ will give ownership of the shared_ptr
to the one who called create_foo
, so C++ knows that there is still something that owns the pointer.
You need to add a get
function that will get the value for you without losing ownership of the pointer, something like this:
extern "C" {
std::shared_ptr<Foo> create_foo() {
// do the thing
}
/* or maybe this
std::shared_ptr<Foo> &&create_foo() {
// do the thing
}
*/
Foo *get_foo(std::shared_ptr<Foo> &foo) {
foo.get();
}
void destroy_foo(std::shared_ptr<Foo> foo) {
}
/* or maybe this
void destroy_foo(std::shared_ptr<Foo> &&foo) {
}
*/
}
Also shared_ptr<Foo>
is not valid C, so I don't know if bindgen and C++ with accept this (probably a warning) but that is already present in your code.
On the Rust side, you could do this:
// must be generated by bindgen and this might create a lot of problems
// this need to be the same struct as the shared_ptr on the C++ side.
// if even one octet is not correct you will run into bugs
// BE SURE that bindgen don't implement Copy for this
struct shared_ptr<T>;
struct Foo(i32);
extern "C" {
fn create_foo() -> shared_ptr<Foo>;
fn get_foo(foo: &shared_ptr<Foo>) -> *mut Foo;
fn destroy_foo(foo: shared_ptr<Foo>);
}
fn main() {
unsafe {
let my_shared_foo = create_foo();
let foo = get_foo(&my_shared_foo);
(*foo).0;
destroy_foo(my_shared_foo);
}
}
Of course this is just an example, and nothing is really safe in any of this. And as I can't test, please let me know if I wrote something that doesn't work. bindgen
should do the job.
I think thatstd::shared_ptr<Foo> &&create_foo()
is wrong and makes no sense at all.
– hellow
Nov 14 '18 at 14:06
@E_net4 Yes I just create something that compile in Rust, but indeed this struct need to match the actual structstd::shared_ptr
if not Rust can't move it correctly.
– Stargateur
Nov 14 '18 at 14:15
@hellow I not sure too, as we tell to rust to use C semantic, I suppose it doesn't but in this casedestroy_foo(std::shared_ptr<Foo> &&foo)
also don't make sense. Because rust will copy the value ? is this work ? I really don't know.
– Stargateur
Nov 14 '18 at 14:19
In you're example if we wrap the shared_ptr<Foo> in a struct and make it opaque then we can maintain ownership and still get the raw ptr eg:struct SmartPtr {
smart_ptr: opaque_blob[u64; 2],
raw_ptr: * mut Foo,
}
– Tom
Nov 15 '18 at 1:16
@Tom That a bad idea to keep the raw pointer separate of shared_ptr, in my exemple I do it but I would advise you to create a structure that hide the raw pointer of shared_ptr.
– Stargateur
Nov 15 '18 at 1:59
|
show 2 more comments
You can return a pointer to a dynamically allocated std::shared_ptr
In the C++ side:
shared_ptr<Foo> create_foo();
extern "C" void *rust_create_foo()
{
shared_ptr<Foo> foo = create_foo();
return static_cast<void*>(new shared_ptr<Foo>(foo));
}
extern "C" void rust_delete_foo(void *ptr)
{
shared_ptr<Foo> *foo = static_cast<shared_ptr<Foo>*>(ptr);
delete foo;
}
And in the Rust side:
extern "C" {
pub fn rust_create_foo() -> *const c_void;
pub fn rust_delete_foo(foo: *const c_void);
}
If you fail to call rust_delete_foo
then the dynamically allocated pointer will leak and so the object will never be deallocated.
Then, when you want to use the object, you will have to write wrapper functions that take that void*
, do the cast and call the appropriate function.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53299119%2fhow-do-you-work-with-a-c-function-that-returns-a-shared-ptrt-when-calling-it%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
std::shared_ptr
is a C++ class and a non-trivial type that can not be exported as is from a library — you need its definition in your target language to conform to the one in C++. To use FFI you need to provide your library functions with a simple C ABI (the C++ ABI is not stable and may change between compiler versions (as might Rust's ABI)) and I doubt that all of functions related to std::shared_ptr
are such, so there is one more obstacle for that.
I'd suggest to return a raw C-pointer from your library instead and own it in Rust.
Even in C++, to load a C++ library you provide C-ABI functions (via extern C
) to gain access to a pointer of your type and then you use it in C++ as how as you want.
So, a few points:
Return a raw C pointer from a function without name mangling so that we know its name and can link to it:
extern "C" Foo* create_foo();
Add a deleter which knows how to properly deallocate the object:
extern "C" void delete_foo(Foo *);
Let the library user (Rust) decide how to own it, for example, by
boxing
the value and using it with atomic reference counter viastd::sync::Arc
(asstd::shared_ptr
does):
extern "C" {
fn create_foo() -> *mut Foo;
fn delete_foo(p: *mut Foo);
}
struct MyFoo {
raw: *mut Foo,
}
impl MyFoo {
fn new() -> MyFoo {
unsafe { MyFoo { raw: create_foo() } }
}
}
impl Drop for MyFoo {
fn drop(&mut self) {
unsafe {
delete_foo(self.raw);
}
}
}
fn main() {
use std::sync::Arc;
let value = Arc::new(MyFoo::new());
let another_value = value.clone();
println!("Shared counter: {}", Arc::strong_count(&value));
}
Let the C++ side forget about owning this pointer - you can't rely on it if it is used from outside the library and you give a raw pointer to it.
If you don't have any access to the library sources, can't do anything with it: the std::shared_ptr
object will not release the pointer ever and we can't make it not to delete the pointer.
3
"C++ abi is not stable" -> "Is not specified"
– hellow
Nov 14 '18 at 11:42
That means it has not been stabilized I guess :)
– Victor Polevoy
Nov 14 '18 at 11:43
It's a difference, because is has not been stabilized means (IMHO), that is about to stabilize and there is a standard for it. Not specified means there no specification at all.
– hellow
Nov 14 '18 at 11:44
Thanks for the suggestion. If I return a raw pointer how do I make sure c++ doesn't free the pointer?Foo * create_foo() { shared_ptr<Foo> smart_p = lib::create_foo(); return smart_p.get(); }
Here smart_p's desctructor will be called at the end of the function and if its the last one it will free the pointer
– Tom
Nov 14 '18 at 11:50
2
TL;DR: How do you work with a C++ shared_ptr<T> in FFI? — you don't.
– Shepmaster
Nov 15 '18 at 15:53
|
show 11 more comments
std::shared_ptr
is a C++ class and a non-trivial type that can not be exported as is from a library — you need its definition in your target language to conform to the one in C++. To use FFI you need to provide your library functions with a simple C ABI (the C++ ABI is not stable and may change between compiler versions (as might Rust's ABI)) and I doubt that all of functions related to std::shared_ptr
are such, so there is one more obstacle for that.
I'd suggest to return a raw C-pointer from your library instead and own it in Rust.
Even in C++, to load a C++ library you provide C-ABI functions (via extern C
) to gain access to a pointer of your type and then you use it in C++ as how as you want.
So, a few points:
Return a raw C pointer from a function without name mangling so that we know its name and can link to it:
extern "C" Foo* create_foo();
Add a deleter which knows how to properly deallocate the object:
extern "C" void delete_foo(Foo *);
Let the library user (Rust) decide how to own it, for example, by
boxing
the value and using it with atomic reference counter viastd::sync::Arc
(asstd::shared_ptr
does):
extern "C" {
fn create_foo() -> *mut Foo;
fn delete_foo(p: *mut Foo);
}
struct MyFoo {
raw: *mut Foo,
}
impl MyFoo {
fn new() -> MyFoo {
unsafe { MyFoo { raw: create_foo() } }
}
}
impl Drop for MyFoo {
fn drop(&mut self) {
unsafe {
delete_foo(self.raw);
}
}
}
fn main() {
use std::sync::Arc;
let value = Arc::new(MyFoo::new());
let another_value = value.clone();
println!("Shared counter: {}", Arc::strong_count(&value));
}
Let the C++ side forget about owning this pointer - you can't rely on it if it is used from outside the library and you give a raw pointer to it.
If you don't have any access to the library sources, can't do anything with it: the std::shared_ptr
object will not release the pointer ever and we can't make it not to delete the pointer.
3
"C++ abi is not stable" -> "Is not specified"
– hellow
Nov 14 '18 at 11:42
That means it has not been stabilized I guess :)
– Victor Polevoy
Nov 14 '18 at 11:43
It's a difference, because is has not been stabilized means (IMHO), that is about to stabilize and there is a standard for it. Not specified means there no specification at all.
– hellow
Nov 14 '18 at 11:44
Thanks for the suggestion. If I return a raw pointer how do I make sure c++ doesn't free the pointer?Foo * create_foo() { shared_ptr<Foo> smart_p = lib::create_foo(); return smart_p.get(); }
Here smart_p's desctructor will be called at the end of the function and if its the last one it will free the pointer
– Tom
Nov 14 '18 at 11:50
2
TL;DR: How do you work with a C++ shared_ptr<T> in FFI? — you don't.
– Shepmaster
Nov 15 '18 at 15:53
|
show 11 more comments
std::shared_ptr
is a C++ class and a non-trivial type that can not be exported as is from a library — you need its definition in your target language to conform to the one in C++. To use FFI you need to provide your library functions with a simple C ABI (the C++ ABI is not stable and may change between compiler versions (as might Rust's ABI)) and I doubt that all of functions related to std::shared_ptr
are such, so there is one more obstacle for that.
I'd suggest to return a raw C-pointer from your library instead and own it in Rust.
Even in C++, to load a C++ library you provide C-ABI functions (via extern C
) to gain access to a pointer of your type and then you use it in C++ as how as you want.
So, a few points:
Return a raw C pointer from a function without name mangling so that we know its name and can link to it:
extern "C" Foo* create_foo();
Add a deleter which knows how to properly deallocate the object:
extern "C" void delete_foo(Foo *);
Let the library user (Rust) decide how to own it, for example, by
boxing
the value and using it with atomic reference counter viastd::sync::Arc
(asstd::shared_ptr
does):
extern "C" {
fn create_foo() -> *mut Foo;
fn delete_foo(p: *mut Foo);
}
struct MyFoo {
raw: *mut Foo,
}
impl MyFoo {
fn new() -> MyFoo {
unsafe { MyFoo { raw: create_foo() } }
}
}
impl Drop for MyFoo {
fn drop(&mut self) {
unsafe {
delete_foo(self.raw);
}
}
}
fn main() {
use std::sync::Arc;
let value = Arc::new(MyFoo::new());
let another_value = value.clone();
println!("Shared counter: {}", Arc::strong_count(&value));
}
Let the C++ side forget about owning this pointer - you can't rely on it if it is used from outside the library and you give a raw pointer to it.
If you don't have any access to the library sources, can't do anything with it: the std::shared_ptr
object will not release the pointer ever and we can't make it not to delete the pointer.
std::shared_ptr
is a C++ class and a non-trivial type that can not be exported as is from a library — you need its definition in your target language to conform to the one in C++. To use FFI you need to provide your library functions with a simple C ABI (the C++ ABI is not stable and may change between compiler versions (as might Rust's ABI)) and I doubt that all of functions related to std::shared_ptr
are such, so there is one more obstacle for that.
I'd suggest to return a raw C-pointer from your library instead and own it in Rust.
Even in C++, to load a C++ library you provide C-ABI functions (via extern C
) to gain access to a pointer of your type and then you use it in C++ as how as you want.
So, a few points:
Return a raw C pointer from a function without name mangling so that we know its name and can link to it:
extern "C" Foo* create_foo();
Add a deleter which knows how to properly deallocate the object:
extern "C" void delete_foo(Foo *);
Let the library user (Rust) decide how to own it, for example, by
boxing
the value and using it with atomic reference counter viastd::sync::Arc
(asstd::shared_ptr
does):
extern "C" {
fn create_foo() -> *mut Foo;
fn delete_foo(p: *mut Foo);
}
struct MyFoo {
raw: *mut Foo,
}
impl MyFoo {
fn new() -> MyFoo {
unsafe { MyFoo { raw: create_foo() } }
}
}
impl Drop for MyFoo {
fn drop(&mut self) {
unsafe {
delete_foo(self.raw);
}
}
}
fn main() {
use std::sync::Arc;
let value = Arc::new(MyFoo::new());
let another_value = value.clone();
println!("Shared counter: {}", Arc::strong_count(&value));
}
Let the C++ side forget about owning this pointer - you can't rely on it if it is used from outside the library and you give a raw pointer to it.
If you don't have any access to the library sources, can't do anything with it: the std::shared_ptr
object will not release the pointer ever and we can't make it not to delete the pointer.
edited Nov 15 '18 at 15:52
Shepmaster
153k14299436
153k14299436
answered Nov 14 '18 at 11:37
Victor PolevoyVictor Polevoy
6,91833692
6,91833692
3
"C++ abi is not stable" -> "Is not specified"
– hellow
Nov 14 '18 at 11:42
That means it has not been stabilized I guess :)
– Victor Polevoy
Nov 14 '18 at 11:43
It's a difference, because is has not been stabilized means (IMHO), that is about to stabilize and there is a standard for it. Not specified means there no specification at all.
– hellow
Nov 14 '18 at 11:44
Thanks for the suggestion. If I return a raw pointer how do I make sure c++ doesn't free the pointer?Foo * create_foo() { shared_ptr<Foo> smart_p = lib::create_foo(); return smart_p.get(); }
Here smart_p's desctructor will be called at the end of the function and if its the last one it will free the pointer
– Tom
Nov 14 '18 at 11:50
2
TL;DR: How do you work with a C++ shared_ptr<T> in FFI? — you don't.
– Shepmaster
Nov 15 '18 at 15:53
|
show 11 more comments
3
"C++ abi is not stable" -> "Is not specified"
– hellow
Nov 14 '18 at 11:42
That means it has not been stabilized I guess :)
– Victor Polevoy
Nov 14 '18 at 11:43
It's a difference, because is has not been stabilized means (IMHO), that is about to stabilize and there is a standard for it. Not specified means there no specification at all.
– hellow
Nov 14 '18 at 11:44
Thanks for the suggestion. If I return a raw pointer how do I make sure c++ doesn't free the pointer?Foo * create_foo() { shared_ptr<Foo> smart_p = lib::create_foo(); return smart_p.get(); }
Here smart_p's desctructor will be called at the end of the function and if its the last one it will free the pointer
– Tom
Nov 14 '18 at 11:50
2
TL;DR: How do you work with a C++ shared_ptr<T> in FFI? — you don't.
– Shepmaster
Nov 15 '18 at 15:53
3
3
"C++ abi is not stable" -> "Is not specified"
– hellow
Nov 14 '18 at 11:42
"C++ abi is not stable" -> "Is not specified"
– hellow
Nov 14 '18 at 11:42
That means it has not been stabilized I guess :)
– Victor Polevoy
Nov 14 '18 at 11:43
That means it has not been stabilized I guess :)
– Victor Polevoy
Nov 14 '18 at 11:43
It's a difference, because is has not been stabilized means (IMHO), that is about to stabilize and there is a standard for it. Not specified means there no specification at all.
– hellow
Nov 14 '18 at 11:44
It's a difference, because is has not been stabilized means (IMHO), that is about to stabilize and there is a standard for it. Not specified means there no specification at all.
– hellow
Nov 14 '18 at 11:44
Thanks for the suggestion. If I return a raw pointer how do I make sure c++ doesn't free the pointer?
Foo * create_foo() { shared_ptr<Foo> smart_p = lib::create_foo(); return smart_p.get(); }
Here smart_p's desctructor will be called at the end of the function and if its the last one it will free the pointer– Tom
Nov 14 '18 at 11:50
Thanks for the suggestion. If I return a raw pointer how do I make sure c++ doesn't free the pointer?
Foo * create_foo() { shared_ptr<Foo> smart_p = lib::create_foo(); return smart_p.get(); }
Here smart_p's desctructor will be called at the end of the function and if its the last one it will free the pointer– Tom
Nov 14 '18 at 11:50
2
2
TL;DR: How do you work with a C++ shared_ptr<T> in FFI? — you don't.
– Shepmaster
Nov 15 '18 at 15:53
TL;DR: How do you work with a C++ shared_ptr<T> in FFI? — you don't.
– Shepmaster
Nov 15 '18 at 15:53
|
show 11 more comments
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to
Foo
and might calls it's deconstructor.
Yes and no. With your actual example. C++ will give ownership of the shared_ptr
to the one who called create_foo
, so C++ knows that there is still something that owns the pointer.
You need to add a get
function that will get the value for you without losing ownership of the pointer, something like this:
extern "C" {
std::shared_ptr<Foo> create_foo() {
// do the thing
}
/* or maybe this
std::shared_ptr<Foo> &&create_foo() {
// do the thing
}
*/
Foo *get_foo(std::shared_ptr<Foo> &foo) {
foo.get();
}
void destroy_foo(std::shared_ptr<Foo> foo) {
}
/* or maybe this
void destroy_foo(std::shared_ptr<Foo> &&foo) {
}
*/
}
Also shared_ptr<Foo>
is not valid C, so I don't know if bindgen and C++ with accept this (probably a warning) but that is already present in your code.
On the Rust side, you could do this:
// must be generated by bindgen and this might create a lot of problems
// this need to be the same struct as the shared_ptr on the C++ side.
// if even one octet is not correct you will run into bugs
// BE SURE that bindgen don't implement Copy for this
struct shared_ptr<T>;
struct Foo(i32);
extern "C" {
fn create_foo() -> shared_ptr<Foo>;
fn get_foo(foo: &shared_ptr<Foo>) -> *mut Foo;
fn destroy_foo(foo: shared_ptr<Foo>);
}
fn main() {
unsafe {
let my_shared_foo = create_foo();
let foo = get_foo(&my_shared_foo);
(*foo).0;
destroy_foo(my_shared_foo);
}
}
Of course this is just an example, and nothing is really safe in any of this. And as I can't test, please let me know if I wrote something that doesn't work. bindgen
should do the job.
I think thatstd::shared_ptr<Foo> &&create_foo()
is wrong and makes no sense at all.
– hellow
Nov 14 '18 at 14:06
@E_net4 Yes I just create something that compile in Rust, but indeed this struct need to match the actual structstd::shared_ptr
if not Rust can't move it correctly.
– Stargateur
Nov 14 '18 at 14:15
@hellow I not sure too, as we tell to rust to use C semantic, I suppose it doesn't but in this casedestroy_foo(std::shared_ptr<Foo> &&foo)
also don't make sense. Because rust will copy the value ? is this work ? I really don't know.
– Stargateur
Nov 14 '18 at 14:19
In you're example if we wrap the shared_ptr<Foo> in a struct and make it opaque then we can maintain ownership and still get the raw ptr eg:struct SmartPtr {
smart_ptr: opaque_blob[u64; 2],
raw_ptr: * mut Foo,
}
– Tom
Nov 15 '18 at 1:16
@Tom That a bad idea to keep the raw pointer separate of shared_ptr, in my exemple I do it but I would advise you to create a structure that hide the raw pointer of shared_ptr.
– Stargateur
Nov 15 '18 at 1:59
|
show 2 more comments
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to
Foo
and might calls it's deconstructor.
Yes and no. With your actual example. C++ will give ownership of the shared_ptr
to the one who called create_foo
, so C++ knows that there is still something that owns the pointer.
You need to add a get
function that will get the value for you without losing ownership of the pointer, something like this:
extern "C" {
std::shared_ptr<Foo> create_foo() {
// do the thing
}
/* or maybe this
std::shared_ptr<Foo> &&create_foo() {
// do the thing
}
*/
Foo *get_foo(std::shared_ptr<Foo> &foo) {
foo.get();
}
void destroy_foo(std::shared_ptr<Foo> foo) {
}
/* or maybe this
void destroy_foo(std::shared_ptr<Foo> &&foo) {
}
*/
}
Also shared_ptr<Foo>
is not valid C, so I don't know if bindgen and C++ with accept this (probably a warning) but that is already present in your code.
On the Rust side, you could do this:
// must be generated by bindgen and this might create a lot of problems
// this need to be the same struct as the shared_ptr on the C++ side.
// if even one octet is not correct you will run into bugs
// BE SURE that bindgen don't implement Copy for this
struct shared_ptr<T>;
struct Foo(i32);
extern "C" {
fn create_foo() -> shared_ptr<Foo>;
fn get_foo(foo: &shared_ptr<Foo>) -> *mut Foo;
fn destroy_foo(foo: shared_ptr<Foo>);
}
fn main() {
unsafe {
let my_shared_foo = create_foo();
let foo = get_foo(&my_shared_foo);
(*foo).0;
destroy_foo(my_shared_foo);
}
}
Of course this is just an example, and nothing is really safe in any of this. And as I can't test, please let me know if I wrote something that doesn't work. bindgen
should do the job.
I think thatstd::shared_ptr<Foo> &&create_foo()
is wrong and makes no sense at all.
– hellow
Nov 14 '18 at 14:06
@E_net4 Yes I just create something that compile in Rust, but indeed this struct need to match the actual structstd::shared_ptr
if not Rust can't move it correctly.
– Stargateur
Nov 14 '18 at 14:15
@hellow I not sure too, as we tell to rust to use C semantic, I suppose it doesn't but in this casedestroy_foo(std::shared_ptr<Foo> &&foo)
also don't make sense. Because rust will copy the value ? is this work ? I really don't know.
– Stargateur
Nov 14 '18 at 14:19
In you're example if we wrap the shared_ptr<Foo> in a struct and make it opaque then we can maintain ownership and still get the raw ptr eg:struct SmartPtr {
smart_ptr: opaque_blob[u64; 2],
raw_ptr: * mut Foo,
}
– Tom
Nov 15 '18 at 1:16
@Tom That a bad idea to keep the raw pointer separate of shared_ptr, in my exemple I do it but I would advise you to create a structure that hide the raw pointer of shared_ptr.
– Stargateur
Nov 15 '18 at 1:59
|
show 2 more comments
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to
Foo
and might calls it's deconstructor.
Yes and no. With your actual example. C++ will give ownership of the shared_ptr
to the one who called create_foo
, so C++ knows that there is still something that owns the pointer.
You need to add a get
function that will get the value for you without losing ownership of the pointer, something like this:
extern "C" {
std::shared_ptr<Foo> create_foo() {
// do the thing
}
/* or maybe this
std::shared_ptr<Foo> &&create_foo() {
// do the thing
}
*/
Foo *get_foo(std::shared_ptr<Foo> &foo) {
foo.get();
}
void destroy_foo(std::shared_ptr<Foo> foo) {
}
/* or maybe this
void destroy_foo(std::shared_ptr<Foo> &&foo) {
}
*/
}
Also shared_ptr<Foo>
is not valid C, so I don't know if bindgen and C++ with accept this (probably a warning) but that is already present in your code.
On the Rust side, you could do this:
// must be generated by bindgen and this might create a lot of problems
// this need to be the same struct as the shared_ptr on the C++ side.
// if even one octet is not correct you will run into bugs
// BE SURE that bindgen don't implement Copy for this
struct shared_ptr<T>;
struct Foo(i32);
extern "C" {
fn create_foo() -> shared_ptr<Foo>;
fn get_foo(foo: &shared_ptr<Foo>) -> *mut Foo;
fn destroy_foo(foo: shared_ptr<Foo>);
}
fn main() {
unsafe {
let my_shared_foo = create_foo();
let foo = get_foo(&my_shared_foo);
(*foo).0;
destroy_foo(my_shared_foo);
}
}
Of course this is just an example, and nothing is really safe in any of this. And as I can't test, please let me know if I wrote something that doesn't work. bindgen
should do the job.
I can't just take the raw pointer because then the C++ code is unaware that I have a reference to
Foo
and might calls it's deconstructor.
Yes and no. With your actual example. C++ will give ownership of the shared_ptr
to the one who called create_foo
, so C++ knows that there is still something that owns the pointer.
You need to add a get
function that will get the value for you without losing ownership of the pointer, something like this:
extern "C" {
std::shared_ptr<Foo> create_foo() {
// do the thing
}
/* or maybe this
std::shared_ptr<Foo> &&create_foo() {
// do the thing
}
*/
Foo *get_foo(std::shared_ptr<Foo> &foo) {
foo.get();
}
void destroy_foo(std::shared_ptr<Foo> foo) {
}
/* or maybe this
void destroy_foo(std::shared_ptr<Foo> &&foo) {
}
*/
}
Also shared_ptr<Foo>
is not valid C, so I don't know if bindgen and C++ with accept this (probably a warning) but that is already present in your code.
On the Rust side, you could do this:
// must be generated by bindgen and this might create a lot of problems
// this need to be the same struct as the shared_ptr on the C++ side.
// if even one octet is not correct you will run into bugs
// BE SURE that bindgen don't implement Copy for this
struct shared_ptr<T>;
struct Foo(i32);
extern "C" {
fn create_foo() -> shared_ptr<Foo>;
fn get_foo(foo: &shared_ptr<Foo>) -> *mut Foo;
fn destroy_foo(foo: shared_ptr<Foo>);
}
fn main() {
unsafe {
let my_shared_foo = create_foo();
let foo = get_foo(&my_shared_foo);
(*foo).0;
destroy_foo(my_shared_foo);
}
}
Of course this is just an example, and nothing is really safe in any of this. And as I can't test, please let me know if I wrote something that doesn't work. bindgen
should do the job.
edited Nov 15 '18 at 15:54
Shepmaster
153k14299436
153k14299436
answered Nov 14 '18 at 13:54
StargateurStargateur
8,59741848
8,59741848
I think thatstd::shared_ptr<Foo> &&create_foo()
is wrong and makes no sense at all.
– hellow
Nov 14 '18 at 14:06
@E_net4 Yes I just create something that compile in Rust, but indeed this struct need to match the actual structstd::shared_ptr
if not Rust can't move it correctly.
– Stargateur
Nov 14 '18 at 14:15
@hellow I not sure too, as we tell to rust to use C semantic, I suppose it doesn't but in this casedestroy_foo(std::shared_ptr<Foo> &&foo)
also don't make sense. Because rust will copy the value ? is this work ? I really don't know.
– Stargateur
Nov 14 '18 at 14:19
In you're example if we wrap the shared_ptr<Foo> in a struct and make it opaque then we can maintain ownership and still get the raw ptr eg:struct SmartPtr {
smart_ptr: opaque_blob[u64; 2],
raw_ptr: * mut Foo,
}
– Tom
Nov 15 '18 at 1:16
@Tom That a bad idea to keep the raw pointer separate of shared_ptr, in my exemple I do it but I would advise you to create a structure that hide the raw pointer of shared_ptr.
– Stargateur
Nov 15 '18 at 1:59
|
show 2 more comments
I think thatstd::shared_ptr<Foo> &&create_foo()
is wrong and makes no sense at all.
– hellow
Nov 14 '18 at 14:06
@E_net4 Yes I just create something that compile in Rust, but indeed this struct need to match the actual structstd::shared_ptr
if not Rust can't move it correctly.
– Stargateur
Nov 14 '18 at 14:15
@hellow I not sure too, as we tell to rust to use C semantic, I suppose it doesn't but in this casedestroy_foo(std::shared_ptr<Foo> &&foo)
also don't make sense. Because rust will copy the value ? is this work ? I really don't know.
– Stargateur
Nov 14 '18 at 14:19
In you're example if we wrap the shared_ptr<Foo> in a struct and make it opaque then we can maintain ownership and still get the raw ptr eg:struct SmartPtr {
smart_ptr: opaque_blob[u64; 2],
raw_ptr: * mut Foo,
}
– Tom
Nov 15 '18 at 1:16
@Tom That a bad idea to keep the raw pointer separate of shared_ptr, in my exemple I do it but I would advise you to create a structure that hide the raw pointer of shared_ptr.
– Stargateur
Nov 15 '18 at 1:59
I think that
std::shared_ptr<Foo> &&create_foo()
is wrong and makes no sense at all.– hellow
Nov 14 '18 at 14:06
I think that
std::shared_ptr<Foo> &&create_foo()
is wrong and makes no sense at all.– hellow
Nov 14 '18 at 14:06
@E_net4 Yes I just create something that compile in Rust, but indeed this struct need to match the actual struct
std::shared_ptr
if not Rust can't move it correctly.– Stargateur
Nov 14 '18 at 14:15
@E_net4 Yes I just create something that compile in Rust, but indeed this struct need to match the actual struct
std::shared_ptr
if not Rust can't move it correctly.– Stargateur
Nov 14 '18 at 14:15
@hellow I not sure too, as we tell to rust to use C semantic, I suppose it doesn't but in this case
destroy_foo(std::shared_ptr<Foo> &&foo)
also don't make sense. Because rust will copy the value ? is this work ? I really don't know.– Stargateur
Nov 14 '18 at 14:19
@hellow I not sure too, as we tell to rust to use C semantic, I suppose it doesn't but in this case
destroy_foo(std::shared_ptr<Foo> &&foo)
also don't make sense. Because rust will copy the value ? is this work ? I really don't know.– Stargateur
Nov 14 '18 at 14:19
In you're example if we wrap the shared_ptr<Foo> in a struct and make it opaque then we can maintain ownership and still get the raw ptr eg:
struct SmartPtr {
smart_ptr: opaque_blob[u64; 2],
raw_ptr: * mut Foo,
}
– Tom
Nov 15 '18 at 1:16
In you're example if we wrap the shared_ptr<Foo> in a struct and make it opaque then we can maintain ownership and still get the raw ptr eg:
struct SmartPtr {
smart_ptr: opaque_blob[u64; 2],
raw_ptr: * mut Foo,
}
– Tom
Nov 15 '18 at 1:16
@Tom That a bad idea to keep the raw pointer separate of shared_ptr, in my exemple I do it but I would advise you to create a structure that hide the raw pointer of shared_ptr.
– Stargateur
Nov 15 '18 at 1:59
@Tom That a bad idea to keep the raw pointer separate of shared_ptr, in my exemple I do it but I would advise you to create a structure that hide the raw pointer of shared_ptr.
– Stargateur
Nov 15 '18 at 1:59
|
show 2 more comments
You can return a pointer to a dynamically allocated std::shared_ptr
In the C++ side:
shared_ptr<Foo> create_foo();
extern "C" void *rust_create_foo()
{
shared_ptr<Foo> foo = create_foo();
return static_cast<void*>(new shared_ptr<Foo>(foo));
}
extern "C" void rust_delete_foo(void *ptr)
{
shared_ptr<Foo> *foo = static_cast<shared_ptr<Foo>*>(ptr);
delete foo;
}
And in the Rust side:
extern "C" {
pub fn rust_create_foo() -> *const c_void;
pub fn rust_delete_foo(foo: *const c_void);
}
If you fail to call rust_delete_foo
then the dynamically allocated pointer will leak and so the object will never be deallocated.
Then, when you want to use the object, you will have to write wrapper functions that take that void*
, do the cast and call the appropriate function.
add a comment |
You can return a pointer to a dynamically allocated std::shared_ptr
In the C++ side:
shared_ptr<Foo> create_foo();
extern "C" void *rust_create_foo()
{
shared_ptr<Foo> foo = create_foo();
return static_cast<void*>(new shared_ptr<Foo>(foo));
}
extern "C" void rust_delete_foo(void *ptr)
{
shared_ptr<Foo> *foo = static_cast<shared_ptr<Foo>*>(ptr);
delete foo;
}
And in the Rust side:
extern "C" {
pub fn rust_create_foo() -> *const c_void;
pub fn rust_delete_foo(foo: *const c_void);
}
If you fail to call rust_delete_foo
then the dynamically allocated pointer will leak and so the object will never be deallocated.
Then, when you want to use the object, you will have to write wrapper functions that take that void*
, do the cast and call the appropriate function.
add a comment |
You can return a pointer to a dynamically allocated std::shared_ptr
In the C++ side:
shared_ptr<Foo> create_foo();
extern "C" void *rust_create_foo()
{
shared_ptr<Foo> foo = create_foo();
return static_cast<void*>(new shared_ptr<Foo>(foo));
}
extern "C" void rust_delete_foo(void *ptr)
{
shared_ptr<Foo> *foo = static_cast<shared_ptr<Foo>*>(ptr);
delete foo;
}
And in the Rust side:
extern "C" {
pub fn rust_create_foo() -> *const c_void;
pub fn rust_delete_foo(foo: *const c_void);
}
If you fail to call rust_delete_foo
then the dynamically allocated pointer will leak and so the object will never be deallocated.
Then, when you want to use the object, you will have to write wrapper functions that take that void*
, do the cast and call the appropriate function.
You can return a pointer to a dynamically allocated std::shared_ptr
In the C++ side:
shared_ptr<Foo> create_foo();
extern "C" void *rust_create_foo()
{
shared_ptr<Foo> foo = create_foo();
return static_cast<void*>(new shared_ptr<Foo>(foo));
}
extern "C" void rust_delete_foo(void *ptr)
{
shared_ptr<Foo> *foo = static_cast<shared_ptr<Foo>*>(ptr);
delete foo;
}
And in the Rust side:
extern "C" {
pub fn rust_create_foo() -> *const c_void;
pub fn rust_delete_foo(foo: *const c_void);
}
If you fail to call rust_delete_foo
then the dynamically allocated pointer will leak and so the object will never be deallocated.
Then, when you want to use the object, you will have to write wrapper functions that take that void*
, do the cast and call the appropriate function.
edited Nov 14 '18 at 17:42
answered Nov 14 '18 at 17:35
rodrigorodrigo
63.7k493129
63.7k493129
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53299119%2fhow-do-you-work-with-a-c-function-that-returns-a-shared-ptrt-when-calling-it%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Is it at all possible for you to design your code in such a way that either C++ or Rust owns the object and manages its lifetime? You could trigger a callback when it is destroyed, to tell the "other" language to stop holding references to it.
– John Zwinck
Nov 14 '18 at 11:32
You can also return a
new shared_ptr<Foo>(...)
... a bit like the doubleBox
trick that is done sometimes from the Rust side.– rodrigo
Nov 14 '18 at 12:06
Just to clarify this is an existing c++ library and I can't control how the pointer is created. I just get given a shared_ptr<Foo> from a function.
– Tom
Nov 14 '18 at 12:12
So you want to be able to access to pointer inner type ?
– Stargateur
Nov 14 '18 at 13:15
@Stargateur yep but I don't want it to be free'd while I'm using it and if I just use the raw pointer I can't guarantee that.
– Tom
Nov 14 '18 at 13:19