2

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)?

j3141592653589793238
  • 1,810
  • 2
  • 16
  • 38

1 Answers1

3

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 "こんいちは"
Alvae
  • 1,254
  • 12
  • 22
  • Thanks, you're right in all points. I still don't get this part though: `Swift arrays are instances of value types, who's lifetime is bound by the stack`. My `buffer` variable is allocated as a pointer, how is it bound to the stack? Another question: Why don't you use `UnsafeMutableBufferPointer` instead of `UnsafeMutablePointer`? @Alvae – j3141592653589793238 Jul 16 '19 at 16:56
  • 1
    @T.Meyer the buffer is a Swift Array. Swift array are value types('They are treated similar to Structs or Enums') backed by Heap storage. Value types get their memory allocated on stack, but if backed by heap storage a part is allocated on heap and I feel this memory gets deallocated when the setter exits. Again I could be wrong here in my assumption. I too feel confused here. – Rohan Bhale Jul 17 '19 at 06:52
  • That is perfectly correct @RohanBhale. As for @T.Meyer's second question, an `UnsafeMutableBufferPointer` wraps a pointer with an int storing its capacity, which I don't need in my example. I personally think it easier to stick to the `Unsafe[Mutable]Pointer` APIs when dealing with C. – Alvae Jul 17 '19 at 08:34