Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

djinni - pointers and circular references between C++ and swift/objective C/java

I have two djinni interfaces, one to be implemented in Swift/objective C/java SwiftObj and one to be implemented in C++ CPPObj.

SwiftObj = interface +o +j {
    someSwiftMethod();
}

CPPObj = interface +c {
    static create(swiftObj: SwiftObj): CPPObj;
    someCPPMethod();
}

They both have a pointer to each other, so the SwiftObj will be able to call someCPPMethod() of the CPPObj and viceversa: CPPObj will be able to call someSwiftMethod() from the SwiftObj:

In swift:

  • the class variable: var myCPPObj: SwiftObj!
  • the creation: myCPPObj = MyCPPObj.create(self)
  • the usage: myCPPObj.someCPPMethod()

In c++:

  • the class variable: shared_ptr<SwiftObj> mySwiftObj_;
  • the usage: mySwiftObj_->someSwiftMethod();

So the question here is, these objects are not getting garbage collected due to the circular reference (I tried and removed the circular reference and they got GCed).

But then I tried setting one of those pointers as weak. In C++: weak_ptr<SwiftObj> mySwiftObj_; ... but this made the mySwiftObj_ be GCed instantly, even while it actually still exists in swift. Same thing happened when I set the swift pointer as weak and the C++ as strong.

So how can I deal with this situation? (other than manually setting one of those pointers as null). Any insights on how the pointers actually work in djinni?

Thanks!

like image 667
Martin Massera Avatar asked Mar 08 '23 20:03

Martin Massera


1 Answers

Unfortunately there is no sort of weak reference/pointer which can understand ownership across languages and Djinni doesn't attempt to add one. The available weak semantics in C++ and Swift only know about references within the same language, which is why you see the instant GC behavior. It's the Djinni-generated proxy object which is being weakly held, and becomes unused, but once the proxy goes away, it releases the real object.

I think the simplest approach would be to split the Swift object into two objects, let's call them Owner and Listener. In your example only Listener needs to be a Djinni object, and implement someSwiftMethod(). Maybe you have other reasons for Owner to be a Djinni interface too. Set up your ownership graph as follows. Forgive the ASCII art: Swift is on the left, C++ on the right.

                  <- Swift|C++ ->

  SwiftOwner ------------------------> CppObj
    ^    |                               |
    |    |                               |
 (weak)  |                               |
    |    v                               |
  SwiftListener <------------------------+

In this scenario, the circularity and weak-refs are all limited to Swift, so will work as you expect, and SwiftListener can forward along methods to SwiftOwner as necessary. This model is optimized for the case where the external usage of these objects is coming from Swift. Such users should hold a reference to SwiftOwner. If your primary usage is in C++ you could reverse the picture, or you could have the external C++ objects hold a strong reference to SwiftOwner. Either way, SwiftOwner has no (strong) circular references to it, and once it's released, the other two objects will be released too.

like image 112
Andrew Avatar answered May 07 '23 15:05

Andrew