1

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.

inc_london
  • 461
  • 5
  • 11

2 Answers2

0

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.

Community
  • 1
  • 1
beshio
  • 794
  • 2
  • 7
  • 17
0

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)
}
OOPer
  • 47,149
  • 6
  • 107
  • 142
  • You should better not call `deinitialize` unless you called `initialize` before. Calling `allocate` will not initialize memory and it's enough to call `deallocate` to balance that. – Mecki Dec 07 '16 at 19:01
  • Well, it's not that it breaks anything. Deinitialize in your case will only do this: Loop over every value, check the value is not NULL, otherwise error, finally set the value to NULL. So unnecessary, yet not wrong or critical. However, the idea is that in case "initialize" has done any special magic, deinitialize now has a chance to undo it. This is currently not really used by Swift as was as I can tell, but it may be used one day in the future and then who knows that happens. – Mecki Dec 08 '16 at 10:23
  • @Mecki, thanks for your comment, but your description of _Deinitialize in my case_ is not accurate. It dose not cause any errors if the values are kept non-NULL, and the values are not set to NULL. I'm not sure why you think `deinitialize` work as you explained. Have you ever seen any Apple's documentation about how `deinitialize` works? – OOPer Dec 08 '16 at 10:37
  • Yes, Swift 3 currently does not enforce the states. For performance reasons the "preconditions" in the documentation are currently not testend and thus not always set, unlike the feature request on that the whole new Unsafe-API bases. But Swift 4 may enforce them and always crash if a precondition is not met. And the Apple docs are incomplete and permanently outdated. Swift is open source, remember? The source code is the only true, accurate and always up-to-date documentation ;-) – Mecki Dec 08 '16 at 11:41
  • @Mecki, I believe you are misunderstanding the description written in the "preconditions" parts. Any of the "preconditions" parts does not say pointers (pointed by `deinitialized` pointer) needs to be NULL, and any of the "postconditions" does not say the pointer content set to NULL. The state is not represented by NULL. – OOPer Dec 08 '16 at 11:51
  • I was not talking about the documentation, I was talking about the proposal that was integrated in Swift 3 and how you would mark a pointer as "not pointing to anything". If you do this in C `char ** a = malloc(sizeof(char *));` you have allocated but not initialized memory (there's memory for `a` but the memory value is undefined). If you now do `*a = "test"` memory is allocated and initialized. How can you now deinitialize this memory or mark it as "not initialized" again w/o deallocating it? You need to make it point "nowhere" as when it points somewhere, it is initialized. – Mecki Dec 08 '16 at 12:29
  • And I'm really not interested in discussing that issue with you here, as apparently you neither read the proposal nor had a look at the source code; this discussion is stupid and leads nowhere. Change your reply or let it be, I don't care. You are right whatever you say, if that makes you feel better. I will just come here when your code crahes in Swift 4 and say "HAHA, told you so". End of discussion. – Mecki Dec 08 '16 at 12:32
  • @Mecki, I almost forget. The first comment of yours was really useful. Thanks for it. – OOPer Dec 08 '16 at 12:47