I want to understand what the recommended way is for string interoperability between swift and c++. Below are the 3 ways to achieve it. Approach 2 is not allowed at work due to restrictions with using std libraries.
Approach 1:
In C++:
char arr[] = "C++ String";
void * cppstring = arr;
std::cout<<"before:"<<(char*)cppstring<<std::endl; // C++ String
// calling swift function and passing the void buffer to it, so that swift can update the buffer content
Module1::SwiftClass:: ReceiveString (cppstring, length);
std::cout<<"after:"<<(char*)cppstring<<std::endl; // SwiftStr
In Swift:
func ReceiveString (pBuffer : UnsafeMutableRawPointer , pSize : UInt ) -> Void
{
// to convert cpp-str to swift-str:
let swiftStr = String (cString: pBuffer.assumingMemoryBound(to: Int8.self));
print("pBuffer content: \(bufferAsString)");
// to modify cpp-str without converting:
let swiftstr:String = "SwiftStr"
_ = swiftstr.withCString { (cString: UnsafePointer<Int8>) in
pBuffer.initializeMemory(as: Int8.self, from: cString, count: swiftstr.count+1)
}
}
Approach 2: The ‘String’ type returned from a swift function is received as ‘swift::String’ type in cpp. This is implicitly casted to std::string type. The std::string has the method available to convert it to char *.
void
TWCppClass::StringConversion ()
{
// GetSwiftString() is a swift call that returns swift::String which can be received in std::string type
std::string stdstr = Module1::SwiftClass::GetSwiftString ();
char * cstr = stdstr.data ();
const char * conststr= stdstr.c_str ();
}
Approach 3:
The swift::String type that is obtained from a swift function can be received in char * by directly casting the address of the swift::String. We cannot directly receive a swift::String into a char *.
void
TWCppClass::StringConversion ()
{
// GetSwiftString() is a swift call that returns swift::String
swift::String swiftstr = Module1::SwiftClass::GetSwiftString ();
// obtaining the address of swift string and casting it into char *
char * cstr = (char*)&swiftstr;
}
I’m not sure my opinion is valuable here. My focus is on technical issues, and from a technical perspective the automatic bridging to std::string
is the right option. If there are policy constraints that prevent you from doing that, that’s not something I can wade in on.
However, if you stick with C strings then I can talk about the best way to do that from Swift. I can think of at least two significant concerns:
- UTF-8 soundness
- Unsafe pointers and copies
Regarding the first, Swift really wants its strings to be valid UTF-8. When you create a Swift string from a C string, Swift will validate the encoding. It’s quite common to see problems in this space. For example, we regularly see things like non-BMP Unicode code points being encoded as a pair of UTF-8 encoded UTF-16 surrogates, which is completely wrong. Swift reacts to such bogosities in two ways:
- Some routines are able to signal the failure, by returning
nil
for example. - Some routines replace problems with the U+FFFD REPLACEMENT CHARACTER.
Check the documentation to make sure you’re getting what you expect.
Regarding the second, when you use unsafe constructs in Swift, you have to be careful to follow the rules. For example, this code is correct:
let s = "Hello Cruel World!"
let c = s.withCString { strdup($0)! }
free(c)
but this code in wrong:
let s = "Hello Cruel World!"
let c = strdup( s.withCString { $0 } )!
free(c)
because the pointer passed to the withCString(_:)
closure must not escape that closure.
Unless you’re using bleeding edge Swift, with lifetime annotations, then it’s not possible to create a bridge like this without copying strings in and out. You should just resign yourself to that.
Beyond that, the ReceiveString(…)
routine you included in your first post doesn’t compile, so I’m not able to fully evaluate it. However, I do have some initial comments:
- It uses
Int8
where it should be usingCChar
. On Apple platforms the latter is a type alias for the former, but C’schar
type is allowed to be unsigned. - If definitely does the wrong this with
swiftstr.count
. That is the count of the number of SwiftCharacter
values in the SwiftString
. But, presumable, your C++ code is expecting you to fillpBuffer
with UTF-8 bytes. That count will be bigger if any of the characters are non-ASCII.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"