pablo
1
Hi there,
I'm trying to use RocksDB from swift after C++ interoperability landed in 5.9.
I added support to build an XCFramework and added the import_as_ref attribute to the DB class, as it has a deleted copy constructor and it is an abstract base class with a single implementation.
This is from RocksDB's setting started:
#include <assert>
#include "rocksdb/db.h"
rocksdb::DB* db;
rocksdb::Options options;
options.create_if_missing = true;
rocksdb::Status status =
rocksdb::DB::Open(options, "/tmp/testdb", &db);
assert(status.ok());
The Open method has the following signature:
static Status Open(const Options& options, const std::string& name, DB** dbptr);
The problem is that it's getting imported into Swift like this:
public class func Open(_ options: rocksdb.Options, _ name: std.string, _ dbptr: rocksdb.DB?!) -> rocksdb.Status
I would expect that the last parameter to be imported as inout rocksDB.DB?
I tested both with Swift 5.9.2 and a Swift 5.10 snapshot from Dec 19.
Is there an attribute or workaround (other than creating a wrapper inside RocksDB) to make this work? Sorry if this is a known issue, I went through the interoperability docs but didn't find anything.
I'm happy to share an Xcode project with the framework integrated that demonstrates the issue.
Thanks!
1 Like
bbrk24
2
Can’t you use _Nonnull and _Nullable to fix this? Like T *_Nullable *_Nonnull, or T *_Nonnull *_Nullable, or whichever version is correct?
If you don’t control the function, though, there’s probably not much you can do about it.
1 Like
etcwilde
(Evan Wilde)
3
You might be able to use API-notes to abuse your way through this if you don't want to modify the source directly: API Notes: Annotations Without Modifying Headers — Clang 18.0.0git documentation
The test-cases are thus far the clearest example of doing all the bad things that I've found. With some finessing, something like this entry from the test might fit your needs?
- Name: OverriddenTypes
Functions:
- Selector: "methodToMangle:second:"
MethodKind: Instance
ResultType: 'char *'
Parameters:
- Position: 0
Type: 'SOMEKIT_DOUBLE *'
- Position: 1
Type: 'float *'
3 Likes
pablo
4
Thanks a lot to both!
API-notes seem very promising to avoid modifying the headers, which is a bit inconvenient in this case as I'd need to fork and sync changes, releases, etc..
That said, I'd be happy if I can get things working via annotations, and then try to integrate API-notes later to see if I can simplify the whole setup.
I tried using the _Nonnull and _Nullable annotations and with both options I get the function imported as:
public class func Open(_ options: rocksdb.Options, _ name: std.string, _ dbptr: rocksdb.DB?) -> rocksdb.Status
I also tried the __attribute__((cf_returns_not_retained)) and __attribute__((cf_returns_retained)). Even though these are intended to work with Core Foundation, I was wondering if they could help here but it looks like they don't, as adding them does not make any changes to the original signature.
This is what I tried (with all s/cf/ns and s/not_/ combinations):
static Status Open(const Options& options, const std::string& name,
DB ** dbptr __attribute__((cf_returns_not_retained)));
Any other ideas or anything else I might be missing?
Thanks!
tera
5
I don't think you'd get "inout", you would get Unsafe[Mutable][Raw]Pointer instead.
- Could you use "Open" with NULL as a last parameter?
- Could you use "DB* db = NULL; Open(... &db);" as the last parameter?
- Can Open modify DB? "const DB* db = ...; Open(..., &db)"
- Can Open modify a DB pointer? "DB* const db = ...; Open(..., &db)"
Depending upon those answers annotate the pointers appropriately.
Open(..., C DB * CN * CN);
Here each C stands for either "const" or empty and each N stands for either _Nullable or _Nonnull (that's 32 possible combinations in total).
pablo
6
Yep, I don't mind, unsafe pointers would work as well but I'm not getting them either, no matter how I annotate the type. I'm wondering if it's a bug / limitation of the C++ interop, that's why I created this thread.
What the function does is to modify the object pointed to by dbptr. This is the implementation.
I guess the correct annotation would be:
Open(..., DB* _Nullable * const _Nonnull);
but this does not make any difference, in this case the function is imported like this:
func Open(..., _: rocksdb.DB?) -> rocksdb.Status
tera
7
Try a simple example to see what's going on. This one compiles fine for me:
// .h
class DB;
DB* getDB(void);
void openDB(DB**);
// .cpp
class DB {};
DB* getDB() {
return new DB();
}
void openDB(DB**) {}
// .swift
var db = getDB()
openDB(&db)
pablo
8
Interesting, thanks for checking the trivial case. I think it is because I'm using SWIFT_UNSAFE_REFERENCE in that class, as I need the type to be imported as a reference type.
#include <swift/bridging>
class DB {
public:
void openDB(DB**);
} SWIFT_UNSAFE_REFERENCE;
is imported as:
@available(macOS 13.3.0, *)
public class DB {
public func openDB(_: DB?!)
}
Is this expected / known? Should I open a bug?
Thanks!
pablo
9