himeshi
(Himeshi)
1
Let's think how to swiftify the following C++ class Foo with c++ interoperability feature.
class Foo {
Foo() { ... }
void doAdditionalConfiguration() { ... }
};
// A function to allocate memory in the heap area and returns the raw pointer
Foo* setupFoo() {
Foo* fooPtr = new Foo();
fooPtr -> doAdditionalConfiguration();
return fooPtr;
}
Foo will be like the following with Swift.
// In "FooModule"
@_expose(Cxx) public struct Foo {
public init() { ... }
public func doAdditionalConfiguration() { ... }
}
In this case, how can we update setupFoo() c++ function? The initializer of the swiftified Foo is FooModule::Foo::init, and the c++ constructor Foo() is disabled. So, we cannot use new FooModule::Foo() syntax to allocate the memory.
Once I came up with the idea of the following code, but it cannot be compiled.
Foo* setupFoo() {
Foo* fooPtr = (Foo*)malloc(sizeof(Foo));
*fooPtr = FooModule::Foo:init(); // object of type 'FooModule::Foo' cannot be assigned because its copy assignment operator is implicitly deleted
fooPtr -> doAdditionalConfiguration();
return fooPtr;
}
How can I write setupFoo function?
Alex_L
(Alex Lorenz)
2
There's no good way to do this because at the moment we can't move the Swift values in C++. Once we have move support in the future, you should be able to do:
new Foo(FooModule::Foo::init());
bbrk24
3
Unlike malloc, calloc returns memory initialized to zeroes. If "all zeroes" is valid for the struct (which it won't always be -- see below), you could do something like this:
@_expose(Cxx) public struct Foo {
// let's say this just holds some integers
var bar: Int
var baz: Int
// and that the default init does this:
public init() {
self.bar = 0
self.baz = 1
}
// Then, C++ code can call this:
public mutating func initializeAssumingZeroed() {
self.baz = 1
}
}
Foo* setupFoo() {
Foo* fooPtr = (Foo*)calloc(1, sizeof(Foo));
fooPtr->initializeAssumingZeroed();
return fooPtr;
}
There are some cases in which "all zeroes" isn't valid. Off the top of my head, this will happen in at least the following scenarios:
- the struct contains a non-nullable reference, e.g.
NSObject, String, or UnsafePointer
- the struct contains an enum with associated values -- this is valid sometimes, but I'm not sure when
Alex_L
(Alex Lorenz)
4
This kind of approach should not be used. It violates the language semantics of both C++ and Swift because both the Swift value and the C++ isn't constructed correctly, so the C++ compiler is free to remove this code as it's a source of undefined behavior. Furthermore, structures that don't have a fixed layout and/or non-trivial structures might end up either having additional fields in C++ that require correct initialization in order to be passed to Swift correctly and/or might actually be allocated on the heap if their layout is not known at compile time.
bbrk24
5
Ah, I wasn’t aware of that. As much as I’ve used Swift and C++ independently, I don’t have much experience with using Swift types in C++.
bbrk24
6
Hm, would something like this work then?
@_expose(Cxx) public struct Foo {
public init() { ... }
public static func allocate() -> UnsafeMutablePointer<Foo> {
let ptr = UnsafeMutablePointer<Foo>.allocate(capacity: 1)
ptr.initialize(to: Foo())
return ptr
}
public static func deallocate(_ ptr: UnsafeMutablePointer<Foo>) {
ptr.deinitialize(count: 1).deallocate()
}
}
Foo* setupFoo() {
Foo* fooPtr = Foo::allocate();
fooPtr->doAdditionalConfiguration();
return fooPtr;
}
himeshi
(Himeshi)
7
Actually I tried it, and surprisingly, allocate was not exported to the module header and not visible from C++.
And, I wonder why a swift struct is not copyable on C++. Since it is a value type and the instance is not managed by ARC or some other memory management systems, it looks like there is not problem to simply copy the struct.
1 Like
bbrk24
8
If any of its fields are, it can't be copied byte-for-byte without retaining that field. A "trivial" struct, like @Alex_L mentioned above, can only have integers, booleans, C structs (but not necessarily other Swift structs), and certain enums. Even Swift's value-semantic collections like String, Set, and Array aren't trivial.
tongjiew
(Tongjie Wang)
9
I believe static methods are currently not supported. Here's the markdown file documenting current C++ interop status: https://github.com/apple/swift/blob/main/docs/CppInteroperability/CppInteroperabilityStatus.md
himeshi
(Himeshi)
10
Interestingly, this static function was properly exported to the module header.
@_expose(Cxx) public struct Foo {
public static func incrementPointee(pointer: UnsafeMutablePointer<UInt32>) {
pointer.pointee += 1
}
}
tongjiew
(Tongjie Wang)
11
Yeah you're right, the document may be outdated. Probably it's UnsafeMutablePointer<Foo> not supported so that the static function is not printed.