Create a String buffer in Swift for C to consume and free later
Two possible options:
let swiftString = "Hello world"
swiftString.withCString { cStringPtr in
// use `cStringPtr` ...
}
calls the closure with a pointer a C string representation of the Swift string (UTF-8 encoded). That pointer is only valid during the execution of the closure.
For a longer lifetime, you can do
let swiftString = "Hello world"
let cStringPtr = strdup(swiftString)
// use `cStringPtr` ...
which allocates a new C String from the given Swift string, and release that memory later with
free(cStringPtr)
Swift String from C String has correct length but incorrect contents
One issue you should observe is this within the C++ function:
Given that this is your scene
definition:
typedef struct {
const char* name;
arrayID roots;
} scene;
Inside the getScene
C++ function, you're doing this:
auto ss = globalObjects.scenes.at(s);
Then you do this later:
scene sr;
sr.name = ss.name.c_str();
//...
return sr;
The issue is that since ss
is a local variable, the ss.name.c_str()
pointer assigned to sr.name
will not be valid when the function returns. Accessing this pointer leads to undefined behavior.
One fix for the problem is to copy the contents of the string to a buffer that is sent by the client program, or copy the contents to a buffer you know is available by the client and won't be invalidated. Since I do not know Swift, you will have to negotiate how to accomplish this.
Another solution would be to make sure you are accessing the same std::string
, not a copy of the std::string
:
auto& ss = globalObjects.scenes.at(s);
Now a reference to the scene
is returned. But then you have the issue of making sure that the string within the scene
is not mutated at all when you actually refer to the returned c_str()
value later on in your application. A C++ programmer would not hold onto the return value of c_str()
for an extended time, since this can lead to hard-to-diagnose bugs.
The bottom line is that always be suspicious of returning a character pointer as a means of returning string data. The usual way that an API returns string data is to have the client supply a buffer and the API fills the buffer with the string data, or less likely, the API allocates memory and returns a pointer to the allocated memory (which leads to complications as the API has to free the memory by some means to avoid memory leaks).
Swift - C++ string via C API
No, there isn't another way.
A std::string
, by design, owns its buffer. If you don't want the data to die when the string does, you'll have to copy it into a new buffer.
In this contrived example there's really no need for the std::string
in the first place, but I'm sure you know that and have a more solid use case.
Swift String to bytes in a buffer
This is how you can set up the buffers and pass them to the C function:
let queryString = "Query ..."
let queryBuffer = Array(queryString.utf8)
var replyBuffer = Array(repeating: UInt8(0), count: 180)
let result = query(queryBuffer, numericCast(queryBuffer.count), &replyBuffer, numericCast(replyBuffer.count))
queryBuffer
is an array with the UTF-8 representation of the string,
and replyBuffer
an array containing a specified amount of zero bytes.
The arrays are passed to the function with queryBuffer
and &replyBuffer
, respectively, which passes pointers to the element storage
to the C function.
If the result buffer is filled with a null-terminated C string on return
then you can create a Swift string with
let resultString = String(cString: replyBuffer)
More Buffer for FileHandler Output?
There are two problems:
- The process may terminate before all data has been read from the pipe.
- Your function blocks the main thread, so that the UI eventually freezes.
Similarly as in How can I tell when a FileHandle has nothing left to be read? you should wait asynchronously for the process to terminate, and also wait for “end-of-file” on the pipe:
func asyncShellExec(path: String, args: [String] = []) {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = [path] + args
let outputPipe = Pipe()
let filelHandler = outputPipe.fileHandleForReading
process.standardOutput = outputPipe
process.launch()
let group = DispatchGroup()
group.enter()
filelHandler.readabilityHandler = { pipe in
let data = pipe.availableData
if data.isEmpty { // EOF
filelHandler.readabilityHandler = nil
group.leave()
return
}
if let line = String(data: data, encoding: String.Encoding.utf8) {
DispatchQueue.main.sync {
self.output_window.string += line
self.output_window.scrollToEndOfDocument(nil)
}
} else {
print("Error decoding data: \(data.base64EncodedString())")
}
}
process.terminationHandler = { process in
group.wait()
DispatchQueue.main.sync {
// Update UI that process has finished.
}
}
}
Related Topics
Swift: Lazily Encapsulating Chains of Map, Filter, Flatmap
Swift and Objectmapper: Nsdate with Min Value
What Is the Markup Format for Documentation on the Parameters of a Block in Swift
How Does Let X Where X.Hassuffix("Pepper") Work
How to Migrate Core Data's Data to App Group's Data
Properly Using Firebase Cloud Functions and Stripe
How to Call a Generic Swift Function When None of the Arguments Provides the Generic Type
Changing Nav Bar Item Programmatically in Swift
How to Test an Optionset with a Switch Statement
Testing Protocol Conformance with Associated Types
Accessibility (Voice Over) with Sprite Kit
Using Animoji/Memoji as Profile Photo
Combined Chart (Line- and Bar Chart) Using iOS-Charts
A Codable Structure Contains a Protocol Property
Saving Exif Data to Jpeg - Swift
What Is Existentialmetatype in Swift
iOS 13 Modals - Calling Swipe Dismissal Programmatically
From Any Utf-16 Offset, Find the Corresponding String.Index That Lies on a Character Boundary