Getting a string allocated in a C function via char** parameter?

I still have a very hard time working with C. In this case, I’d like to call a SQLite function that sometimes allocates an error string internally and passes it back via char** parameter:

int sqlite3_exec(
  sqlite3*,                                  /* An open database */
  const char *sql,                           /* SQL to be evaluated */
  int (*callback)(void*,int,char**,char**),  /* Callback function */
  void *,                                    /* 1st argument to callback */
  char **errmsg                              /* Error msg written here */
);

public
func
sqlite3_exec(_: OpaquePointer!,
	_ sql: UnsafePointer<Int8>!,
	_ callback: (@convention(c) (UnsafeMutableRawPointer?,
								Int32,
								UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?,
								UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?) -> Int32)!,
	_: UnsafeMutableRawPointer!,
	_ errmsg: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>!) -> Int32

This string is freed with a call to void sqlite3_free(void*);

How can I get this as a Swift String?

Also, Xcode 12.3 gives this helpful diagnostic (obviously that code won’t work, was just experimenting and noticed the diagnostic):

errMsg wants to be a place where sqlite3_exec can write back a pointer.

func exec(sql inSQL: String) throws
{
  var errMsg: UnsafeMutablePointer<Int8>? = nil
  let result = sqlite3_exec(nil, inSQL, nil, nil, &errMsg)
  if result != 0
  {
    let message = errMsg.map({ String(cString: UnsafeRawPointer($0).assumingMemoryBound(to: UInt8.self)) }) ?? ""
    // call sqlite3_free here
    try throwIfNotOK(result, message)
  }
}

Unfortunately Int8 is not particularly comfortable to work with for strings, but there you are.

1 Like

Wow. I could’ve sworn that was the first thing I tried, but maybe not. Thanks!

Using withMemoryRebound would be better; the earlier is probably fine for this particular case, but isn't the general solution.

let message = errMsg.map { chars -> String in
  let count = strlen(chars)
  let message = chars.withMemoryRebound(to: UInt8.self, capacity: count) { String(cString: $0) }
  sqlite3_free(chars)
  return message
}
2 Likes