I have the following C struct:
typedef struct {
char** categories;
int category_size;
} category_fmc_s_type;
My Swift array has the following values:
let categories = ["Weekday", "Weekend"]
I want to populate the C Struct field 'categories' with 'Weekday' & 'Weekend'. To do this I call my toPointer():
fileprivate static func toPointer(_ args: [String]) -> UnsafeMutablePointer<UnsafeMutablePointer<Int8>> {
let buffer = UnsafeMutablePointer<UnsafeMutablePointer<Int8>>.allocate(capacity: args.count)
for (index, value) in args.enumerated() {
buffer[index] = UnsafeMutablePointer<Int8>(mutating: (value as NSString).utf8String!)
}
return buffer
}
I keep getting the following XCode 8 error:
Cannot convert value of type 'UnsafeMutablePointer<UnsafeMutablePointer<Int8>>' to expected argument type 'UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>!'
Any suggestions? I don't understand why there is the optional and '!' in the C-Struct definition implicitly.
As the compiler emits as an error, you need to unwrap after Int8 w/ "?" as follows.
fileprivate func toPointer(_ args: [String]) -> UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> {
let buffer = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: args.count)
for (index, value) in args.enumerated() {
buffer[index] = UnsafeMutablePointer<Int8>(mutating: (value as NSString).utf8String!)
}
return buffer
}
then,
func testMyCat() {
let categories = ["Weekday", "Weekend"]
let buffer = toPointer(categories)
var mycat = category_fmc_s_type()
mycat.categories = buffer // you would see compile error w/o "?"
}
the code above works w/o error. Martin's solution gives a compile error at
mycat.categories = &cargs (see the link)
I don't know why.
Check the reference of utf8String property of NSString:
Discussion
This C string is a pointer to a structure inside the string object,
which may have a lifetime shorter than the string object and will
certainly not have a longer lifetime. Therefore, you should copy the
C string if it needs to be stored outside of the memory context in
which you use this property.
The term memory context is not well-defined, but one thing sure is that you cannot expect the allocated region for the C string would live forever. When the member categories in the category_fmc_s_type is accessed, the pointers may be pointing to the already freed regions.
Applying the suggestion from Martin R to your code, your code would be like this:
fileprivate static func toPointer(_ args: [String]) -> UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> {
let buffer = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: args.count)
buffer.initialize(from: args.lazy.map{strdup($0)})
return buffer
}
And remember, after you finish using the category_fmc_s_type, you need to deallocate the regions allocated by strdup(_:) and UnsafeMutablePointer.allocate(capacity:):
fileprivate static func freePointer(_ pointers: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>, count: Int) {
for i in 0..<count {
free(pointers[i])
}
pointers.deinitialize(count: count)
pointers.deallocate(capacity: count)
}
Related
Here is the function in swift to convert from a swift string to a C string
func swiftStringToCString(swiftString: String) -> UnsafeMutablePointer<CString>?{
let convertedCString: [CChar]? = swiftString.cString(using: .utf8)
if let safeConvertedCString = convertedCString {
var cString = UnsafeMutablePointer<CString>.allocate(capacity: 1)
//defer {
// cString.deallocate()
//}
cString.pointee.count = UInt32(safeConvertedCString.count) - 1
cString.pointee.data = UnsafePointer<Int8>(safeConvertedCString)
return cString
}
else
{
return nil
}
}
The CString struct is defined in a C header file:
typedef struct {
const char *data;
uint32_t count;
} CString;
I also have an addition test function which simply prints out the string passed in:
extern void __cdecl testCString(CString *pCString);
When I call
testCString(swiftStringToCString(swiftString: "swiftString"))
This gets printed out:
wiftString
I also noticed that I get the warning
Initialization of 'UnsafePointer<Int8>' results in a dangling pointer
when I do
cString.pointee.data = UnsafePointer<Int8>(safeConvertedCString)
This approach is incorrect. You can't just hold onto pointers into a String's internal storage and expect it to stick around past the current line of code. That's what the warning is telling you.
In order to ensure that a pointer is valid, you need to use .withUnsafeBufferPointer. I would expect something along these lines:
"swiftString".utf8CString.withUnsafeBufferPointer { buffer in
var cString = CString(data: buffer.baseAddress!, count: UInt32(buffer.count))
testCString(&cString);
}
This ensures that the utf8 buffer exists until the end of the block. If you want to hold onto it beyond that, you're going to need to add code to copy those bytes into memory you allocate and release yourself.
I am working with some mixed C-Swift code that has a construction for converting a C-string in a struct to a Swift string like so:
struct MyCStruct { // from the c header file
char string[75];
}
extension MyCStruct {
var swiftString: String {
get { return String(cString: UnsafeRawPointer([self.string]).assumingMemoryBound(to: CChar.self)) }
}
}
As of Swift 5.1, I started getting a sporadic memory overlap exception, possibly triggered by memory allocation elsewhere or a thread context switch. I was able to fix it by changing the get to:
get { return withUnsafePointer(to: self.string) { ptr -> String in
return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self))
} }
But, I am wondering is this just masking some deeper problem unrelated to the original construction? Or, is the original construction faulty, and just lucky it never crashed before?
Your method relies on undefined behavior. Here
UnsafeRawPointer([self.string])
a temporary array is created, and the address of its element storage is passed to the UnsafeRawPointer() initializer. That memory address is only valid for the duration of of the init call, but must not be used when the initializer returns.
What you can do is to get a pointer to the string variable itself (which is mapped as a tuple to Swift) and rebind that (compare Converting a C char array to a String):
extension MyCStruct {
var swiftString: String {
withUnsafePointer(to: self.string) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: self.string)) {
String(cString: $0)
}
}
}
}
(It is assumed – as in your original code – that the C array of character is null-terminated.)
I'm basically trying to wrap a C structure within a Swift class.
The Swift class has an instance property of type of the C structure.
The C structure contains several properties which are of type const char *.
In order to assign values to the structure, I wrote getters and setters for each property:
public class MyClass: NSObject {
private var configuration = c_structure_config()
}
// Function of MyClass
// f.ex. on "registrationUri:"
private(set) public var registrationUri: String {
get {
return String(cString: configuration.reg_uri)
}
set {
if (configuration.reg_uri != nil) {
configuration.reg_uri.deallocate()
}
let maxLength = newValue.count + 1 // +1 for NULL
var buffer: [CChar] = [CChar](UnsafeMutableBufferPointer<Int8>.allocate(capacity: maxLength))
guard newValue.getCString(&buffer, maxLength: maxLength, encoding: .utf8) == true else {
fatalError("Could not allocate memory for Account Config reg uri")
}
// "configuration" is the instance property (see above)
// reg_uri is of type char const *
configuration.reg_uri = UnsafePointer<Int8>(buffer)
}
}
However, this approach leads to weird crashes and error reports complaining about pointers overlapping range (Fatal error: UnsafeMutablePointer.initialize overlapping range).
I know that I'm deallocating and allocating memory whenever the string is set and that that's probably not very efficient. I haven't found a better solution so far though.
What's wrong here (or is this right, I made a wrong assumption and I gotta search somewhere else)?
There are a couple of problems with your solution.
First, it is ill-advised to use String.count to count to get the size of the underlying C buffer. The reason is that String.count returns the number of actual characters of your string, and not the number of bytes used to represent it. Not all characters fit the 256 bits of Int8 (a.k.a. CChar). Hence, your code will probably crash if you attempt setting your property with non-ascii characters. It is better to use the String.utf8CString property, which returns a contiguous array of CChar.
Second, since your buffer is allocated within the setter, it will be deallocated when your setter returns (Swift arrays are instances of value types, who's lifetime is bound by the stack). That means the pointer corresponding to buffer's base is actually invalidated when your setter returns. I suspect this is the reason why the error you reported occurs at runtime.
Finally, please do not test for true in guards and if statements.
Here is a corrected version:
var registrationUri: String {
get {
return String(cString: reg_uri)
}
set {
if (reg_uri != nil) {
reg_uri.deallocate()
}
newValue.withCString { cString in
// No need to add 1, newValue.utf8CString already has the correct buffer capacity.
let capacity = newValue.utf8CString.count
let buffer: UnsafeMutablePointer<Int8> = .allocate(capacity: capacity)
buffer.assign(from: cString, count: capacity)
reg_uri = UnsafePointer(buffer)
}
}
}
// ...
myInstance.registrationUri = "こんいちは"
print(myInstance.registrationUri)
// Prints "こんいちは"
I'm calling a C library from Swift 4 and I have troubles converting a [String] to const char *[].
The C API defines this method:
int getDREFs(const char* drefs[], unsigned char count);
which is exposed in Swift as
public func getDREFs(_ drefs: UnsafeMutablePointer<UnsafePointer<Int8>?>!, _ count: UInt8) -> Int32
The Swift wrapper I'm trying to write is the following
public func get(drefs: [String]) throws {
var cDrefs = [UnsafePointer<Int8>]()
for dref in drefs {
cDrefs.append(dref.cString(using: .utf8)!)
}
let pDrefs = UnsafeMutablePointer<UnsafePointer<Int8>>(&cDrefs)
getDREFFs(pDrefs, drefs.count)
}
but the error I get is
Cannot convert value of type 'UnsafeMutablePointer<UnsafePointer<Int8>>' to expected argument type 'UnsafeMutablePointer<UnsafePointer<Int8>?>!'
what am I missing?
getDREFSs expects a pointer to an array of optional Int8 pointers.
Also the second argument must be converted to UInt8.
So this would compile:
public func get(drefs: [String]) -> Int {
var cDrefs = [UnsafePointer<Int8>?]()
for dref in drefs {
cDrefs.append(dref.cString(using: .utf8))
}
let result = getDREFs(&cDrefs, UInt8(drefs.count))
return Int(result)
}
But a quick test shows that is does not work if called with
multiple strings. The reason is that the arrays
returned by dref.cString(using: .utf8)
can already be deallocated (and the pointer invalid)
when the C function is called.
Here is a working version, a slight modification of
Convert a Swift Array of String to a to a C string array pointer for
this particular case:
public func get(drefs: [String]) -> Int {
var cargs = drefs.map { UnsafePointer<Int8>(strdup($0)) }
let result = getDREFs(&cargs, UInt8(drefs.count))
for ptr in cargs { free(UnsafeMutablePointer(mutating: ptr)) }
return Int(result)
}
In Swift I am using C API that returns struct with char array (containing UTF8 null terminated string or null).
struct TextStruct {
char * text;
//other data
}
I use:
let text: String = String(cString: data.text)
This works, however, when data.text is nullptr, this fails with
fatal error: unexpectedly found nil while unwrapping an Optional value
Is there any workaround, or I have to check data.text manually before using cString ctor?
In addition to Gwendal Roué's solution: You can
annotate the C API to indicate whether the pointer can be null or not.
For example,
struct TextStruct {
char * _Nullable text;
//other data
};
is imported to Swift as
public struct TextStruct {
public var text: UnsafeMutablePointer<Int8>?
// ...
}
where var text is a "strong" optional instead of an implicitly
unwrapped optional. Then
let text = String(cString: data.text)
// value of optional type 'UnsafeMutablePointer<Int8>?' not unwrapped; ...
no longer compiles, and forces you to use optional binding or
other unwrapping techniques, and the "fatal error: unexpectedly found nil"
cannot happen anymore accidentally.
For more information, see "Nullability and Objective-C" from the Swift Blog –
despite the title, it can be used with pure C as well.
Yes, you have to check data.text, in order to make sure it can feed the String(cString:) initializer, which is documented to require a non-null pointer.
A technique is to use a if let statement. This is a classic technique for safely unwrapping optional values:
let str: String?
if let ptr = ptr {
str = String(cString: ptr)
} else {
str = nil
}
print("got \(str ?? "nil")")
Another technique is the Optional.map function:
let str = ptr.map { String(cString: $0) }
print("got \(str ?? "nil")")
You can check if the address of the pointer is set
let textIsSet = Int(bitPattern: data.text) != 0
let text: String? = textIsSet ? String(cString: data.text) : nil