Hi. Given Swift 6.1 and c++20, can someone show an example how to properly use SWIFT_SHARED_REFERENCE macro? (I am struggling to understand what kind of parameters this macro accepts) Let's have a concrete example:
#include <swift/bridging>
using TcpSessionPtr = std::shared_ptr<TcpSession>;
class TcpSession final : public std::enable_shared_from_this<TcpSession> {
// skipping code
TcpSession(asio::strand<asio::any_io_executor>& strand, const TcpConfig& config) {}
~TcpSession() {}
TcpSessionPtr static shared(asio::strand<asio::any_io_executor>& strand, const TcpConfig& config) {
return std::make_shared<TcpSession>(strand, config);
}
}
Can I make Swift import TcpSessionPtr as a class TcpSession?
(I do not think the shared static method will be called directly from Swift, only its result is expected to be passed to a Swift function)
You'll have to have a refCount field (ideally of type std::atomic) on your TcpSession class, then add SWIFT_SHARED_REFERENCE after the ending class bracket, like this:
If you use SWIFT_RETURNS_RETAINED, make sure that your refCount starts at 1. Otherwise, it can start at 0. If you need to create your class instance from Swift, you can call the .shared function:
let session = TcpSession.shared(arg1, arg2)
Or, you can create it from C++ and then access the shared_ptr from Swift instead. However you need.
I think this should work fine with std::shared_ptr, but I'm curious to see how it works.
Thanks. This gave a bit more clarity to the docs. After some experimenting, I think I have managed to create a basic example with the shared_ptr. Here is a repo I created: https://github.com/RussBaz/swift-cpp-interop I am still figuring it out, however.
I have found something surprising though - my class is released twice but retained only once. Needs more researching.
I think I could have skipped SWIFT_SHARED_REFERENCE entirely and just use the shared_ptr. But then it will be exposed as a pointer to the swift side, and I was not sure about the ergonomics of it.
Actually on second thought, if you are using std::shared_ptr then you shouldn't have to make the object a SWIFT_SHARED_REFERENCE. Depending on how you are using it in Swift, it may work fine to just be a std::shared_ptr.
If you need to access .pointee from Swift, though, be careful- there's a bug or unintended behavior where that creates a copy instead of borrowing.
Is this is a correct implementation of releaseSession? There’s a hazard where one thread can suspend at any point before the second if statement, another thread can run releaseSession to completion, decrementing obj->refCount to 0 and deleting obj, and then the first thread resumes and tries to dereference obj which is now a dangling pointer. This would indicate an overrelease bug in the client, but the whole purpose of checking if refCount > 0 seems to be to handle that. (That comparison also implies that refCount is a signed integer, which also seems to be a decision to handle overrelease.)
I'm going to say this probably isn't the best. I'm currently using this implementation on my app but it's pretty much not concurrent in that different threads aren't messing with my shared references. So, if there's a suggestion for a better release method, let me know...
This could be implemented generically, like Russ' generic SwiftSharedBase so you wouldn't need to add the atomic member and reference count implementation per class.