Passing string between Swift and C++

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;
}
Answered by DTS Engineer in 851314022

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 using CChar. On Apple platforms the latter is a type alias for the former, but C’s char type is allowed to be unsigned.
  • If definitely does the wrong this with swiftstr.count. That is the count of the number of Swift Character values in the Swift String. But, presumable, your C++ code is expecting you to fill pBuffer 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"

It’s hard to recommend an approach without first understanding the rules as to what you think of as ‘best’. Speaking personally, I value reliability and safety, so I tend to use high-level abstractions where possible.

In the case of C++ / Swift interoperability, that means using the automatic bridging between Swift’s String and C++’s std::string. Which you’ve outlined in approach 2, but it seem that you’re not allowed to use that:

Approach 2 is not allowed at work due to restrictions with using std libraries.

Given that, it seems that you’re not actually asking about bridging between C++ strings and Swift but between C strings and Swift, right?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

As you mentioned, we too see 'best' as a 'reliable and safe' approach.

Yes, we're mainly concerned with bridging between C strings and Swift Strings. The implicit conversion provided in Approach 2 uses std::string and while we can obtain the C string from this std::string, we're not supposed to use the std data structures - so maybe approach 3 or 1 is the way to go - or something different that is not known to us yet.

What would you recommend?

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 using CChar. On Apple platforms the latter is a type alias for the former, but C’s char type is allowed to be unsigned.
  • If definitely does the wrong this with swiftstr.count. That is the count of the number of Swift Character values in the Swift String. But, presumable, your C++ code is expecting you to fill pBuffer 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"

Passing string between Swift and C&#43;&#43;
 
 
Q