Swift: Create Array of Pointers For Calling a C Function - swift

Context
From Swift, I am trying to call a specific function of the libxlsxwriter C library. The documentation is here: https://libxlsxwriter.github.io/worksheet_8h.html#a62bf44845ce9dcc505bf228999db5afa
The function assembles a "rich string" (the equivalent of an AttributedString, where different formats/styles apply to different ranges) and writes it to a specific cell in the Excel worksheet. In C, the function works like this:
lxw_format *bold = workbook_add_format(workbook);
format_set_bold(bold);
lxw_format *italic = workbook_add_format(workbook);
format_set_italic(italic);
lxw_rich_string_tuple fragment11 = {.format = NULL, .string = "This is " };
lxw_rich_string_tuple fragment12 = {.format = bold, .string = "bold" };
lxw_rich_string_tuple fragment13 = {.format = NULL, .string = " and this is "};
lxw_rich_string_tuple fragment14 = {.format = italic, .string = "italic" };
lxw_rich_string_tuple *rich_string1[] = {&fragment11, &fragment12,
&fragment13, &fragment14, NULL};
worksheet_write_rich_string(worksheet, CELL("A1"), rich_string1, NULL);
The Problem
I have an array of lxw_rich_string_tuple structs, but I'm unclear how to convert this into the array of pointers that worksheet_write_rich_string() accepts:
// The worksheet object and `lxw_format` objects are already existent.
// This array contains multiple well-formed `lxw_rich_string_tuple` structs, which I can see in the debugger.
//
var rawTuples: [lxw_rich_string_tuple] = ...
// Parameters:
// - the worksheet on which to write this string
// - the row of the cell in which to write
// - the column of the cell in which to write
// - a null-terminated array of pointers to `lxw_rich_string_tuple` structs
// - an optional format object to use, null in this case.
//
worksheet_write_rich_string(worksheet, 0, 1, ?, NULL);
The trouble is the ?. I've tried all sorts of withUnsafeBytes and withUnsafeMutableBytes and UnsafeMutableRawPointer().bindMemory(to:capacity:) and I cannot figure out the magic Swift gibberish to do what is such a SIMPLE thing in C. Thanks.
My Attempt
This gives no compiler errors, but crashes with a Bad Access exception:
let argsSize: Int = rawTuples.count
rawTuples.withUnsafeMutableBufferPointer { rawTuplesPointer in
let ptr = UnsafeMutableRawPointer(rawTuplesPointer.baseAddress!).bindMemory(to: lxw_rich_string_tuple.self, capacity: argsSize)
var tuplePointers: [UnsafeMutablePointer<lxw_rich_string_tuple>?] = []
for i in 0 ..< argsSize
{
let tp: UnsafeMutablePointer<lxw_rich_string_tuple>? = ptr + (i * MemoryLayout<lxw_rich_string_tuple>.stride)
tuplePointers.append(tp)
}
tuplePointers.append(nil)
tuplePointers.withUnsafeBufferPointer { tpPointer in
let m = UnsafeMutablePointer(mutating: tpPointer.baseAddress)
worksheet_write_rich_string(lxw_worksheet, cell.row, cell.col, m, nil)
}
}

Try this:
rawTuples.withUnsafeMutableBufferPointer { p in
guard let arrBaseAddress = p.baseAddress else { return }
var pointersToEachArrayElement: [UnsafeMutablePointer<_>?] =
Array(arrBaseAddress ..< arrBaseAddress.advanced(by: p.count))
pointersToEachArrayElement.append(nil)
pointersToEachArrayElement.withUnsafeMutableBufferPointer { q in
// use q.baseAddress in the call to worksheet_write_rich_string
}
}
The idea is similar to your attempt - to create an array of pointers to each of the array elements. But unlike your attempt, I avoided calling the initialisers of the pointer types (which I don't think you are supposed to do), and instead tried to use the withXXX functions as much as I could.
You should also consider just writing Objective-C wrappers around the C function and lxw_rich_string_tuple. Sometimes C functions are just not bridged into Swift in a very convenient way, and this is not the first time I've experienced something like this, unfortunately :(

Related

Convert Swift String to wchar_t

For context: I'm trying to use the very handy LibXL. I've used it with success in Obj-C and C++ but am now trying to port over to Swift. In order to better support Unicode, I need to sent all strings to the LibXL api as wchar_t*.
So, for this purpose I've cobbled together this code:
extension String {
///Function to convert a String into a wchar_t buffer.
///Don't forget to free the buffer!
var wideChar: UnsafeMutablePointer<wchar_t>? {
get {
guard let _cString = self.cString(using: .utf16) else {
return nil
}
let buffer = UnsafeMutablePointer<wchar_t>.allocate(capacity: _cString.count)
memcpy(buffer, _cString, _cString.count)
return buffer
}
}
The calls to LibXL appear to be working (getting a print of the error messages returns 'Ok'). Except when I try to actually write to a cell in a test spreadsheet. I get can't write row 0 in trial version:
if let name = "John Doe".wideChar, let passKey = "mac-f.....lots of characters...3".wideChar {
xlBookSetKeyW(book, name, passKey)
print(">: " + String.init(cString: xlBookErrorMessageW(book)))
}
if let sheetName = "Output".wideChar, let path = savePath.wideChar, let test = "Hello".wideChar {
let sheet: SheetHandle = xlBookAddSheetW(book, sheetName, nil)
xlSheetWriteStrW(sheet, 0, 0, test, sectionTitleFormat)
print(">: " + String.init(cString: xlBookErrorMessageW(book)))
let success = xlBookSaveW(book, path)
dump(success)
print(">: " + String.init(cString: xlBookErrorMessageW(book)))
}
I'm presuming that my code for converting to wchar_t* is incorrect. Can someone point me in the right direction for that..?
ADDENDUM: Thanks to #MartinR for the answer. It appears that the block 'consumes' any pointers that are used in it. So, for example, when writing a string using
("Hello".withWideChars({ wCharacters in
xlSheetWriteStrW(newSheet, destRow, destColumn, wCharacters, aFormatHandle)
})
The aFormatHandle will become invalid after the writeStr line executes and isn't re-useable. It's necessary to create a new FormatHandle for each write command.
There are different problems here. First, String.cString(using:) does
not work well with multi-byte encodings:
print("ABC".cString(using: .utf16)!)
// [65, 0] ???
Second, wchar_t contains UTF-32 code points, not UTF-16.
Finally, in
let buffer = UnsafeMutablePointer<wchar_t>.allocate(capacity: _cString.count)
memcpy(buffer, _cString, _cString.count)
the allocation size does not include the trailing null character,
and the copy copies _cString.count bytes, not characters.
All that can be fixed, but I would suggest a different API
(similar to the String.withCString(_:) method):
extension String {
/// Calls the given closure with a pointer to the contents of the string,
/// represented as a null-terminated wchar_t array.
func withWideChars<Result>(_ body: (UnsafePointer<wchar_t>) -> Result) -> Result {
let u32 = self.unicodeScalars.map { wchar_t(bitPattern: $0.value) } + [0]
return u32.withUnsafeBufferPointer { body($0.baseAddress!) }
}
}
which can then be used like
let name = "John Doe"
let passKey = "secret"
name.withWideChars { wname in
passKey.withWideChars { wpass in
xlBookSetKeyW(book, wname, wpass)
}
}
and the clean-up is automatic.

How to create a pointer in Swift?

I'm working with Swift 3.
I would like to have this C syntax :
int myVar;
int *pointer = &myVar;
So modifying pointer or myVar does the same exact same thing.
Also I don't know if it makes any difference, but in my case myVar is an array containing elements of a class and pointer is a pointer to one element of this array.
The & also exists in Swift but can only be used as part of a parameter list (e.g. init, func, closure).
var i = 5
let ptr = UnsafeMutablePointer(&i)
print(ptr.pointee) // 5
// or
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
ptr.initialize(to: 5)
// or with a closure
let ptr: UnsafePointer = { $0 }(&i)
(Assuming I understand what you're asking for....)
Try the following code in a playground. It should print "99" three times.
class Row {
var rowNumber = 0
}
var rows = [Row]()
let testRow = Row()
testRow.rowNumber = 1
rows.append(testRow)
let selectedRow = rows[0]
selectedRow.rowNumber = 99
print(testRow.rowNumber)
print(selectedRow.rowNumber)
print(rows[0].rowNumber)
By default, there's no copying of objects as part of an assignment statement. If it were a struct, that would be different.
Adding a bit for completeness:
If you want a similar effect with scalar values instead of objects, Swift supplies various types of wrappers.
let intPointer = UnsafeMutablePointer<Int>.allocate(capacity: 8) // Should be 1, not 8 according to comment re: docs
let other = intPointer
other.pointee = 34
print(intPointer.pointee)
(Warning: I haven't used these wrappers for anything except experimenting in a playground. Don't trust it without doing some research.)
Same example as #Phillip. But I used struct. In this example rows[0] won't change:
struct Row {
var rowNumber = 0
}
var rows = [Row]()
var testRow = Row()
testRow.rowNumber = 1
rows.append(testRow)
var selectedRow = rows[0]
selectedRow.rowNumber = 99
print(testRow.rowNumber) // prints 1
print(selectedRow.rowNumber) // prints 99
print(rows[0].rowNumber) // prints 1
There are no C style pointers (Unsafe Pointer) as the question asks however objects are shared by reference and structures are by value:
Swift assign, pass and return a value by reference for reference type and by copy for Value Type
structures are always copied when they are passed around in your code, but classes are passed by reference.
For example
How to have pointers/ references to objects
class Song {
init(title: String, image: String, file: String, volume: Float, queuePlayer: AVQueuePlayer, playerLooper: AVPlayerLooper?) {
self.title = title
self.image = image
...
}
var title: String
var image: String
...
}
var aSong = Song(title: "", image: "", ...)
var arrOfSongReferences: [Song] = [Song]()
arrOfSongReferences.append(aSong)
var ptrToASong: Song = aSong
aSong = nil
// Due to Swift garbage collection ARC (Automatic Reference Counting), we still have references to the original aSong object so it won't be deleted
If data is struct you cannot do this
struct Song {
var title: String
var image: String
...
}
var aSong: Song = Song(title: "", image: "", ...)
var copyOfASong: Song = aSong
Method
You can also pass by reference into a function
// this would be inside a class, perhaps Player. It doesn't have to be a static btw
static func playSound(_ sound: inout Song, volume: Float = 0.0) {
if (sound.playerLooper == nil) {
...
}
}
// usage
Player.playSound(sound: &aSong)

Cannot convert value of type 'NSRange'(aka'_NSRange') to expected argument type 'Range<Data.Index>' (aka 'Range<int>

there. I am a beginner in Swift and am trying to convert an older program to Swift3. I´ve managed to fix a bunch of errors, but I cannot get this function to work.
fileprivate func extractEntitlements(_ entitlementData: Data) -> NSDictionary? {
var originalEntitlementsData = entitlementData
let xmlStart = "<?xml".data(using: String.Encoding.ascii, allowLossyConversion: true)
let bytes = (originalEntitlementsData as NSData).bytes
for i in 0...(originalEntitlementsData.count - xmlStart!.count) {
if memcmp((xmlStart! as NSData).bytes, bytes + i, Int(xmlStart!.count)) == 0 {
let end = originalEntitlementsData.count - i
**originalEntitlementsData = originalEntitlementsData.subdata(in: NSMakeRange(i, end))**
break;
}
}
return NSString(data: originalEntitlementsData, encoding: String.Encoding.ascii.rawValue)?.propertyList() as? NSDictionary
}
Here is the error I get:
There are a bunch of questions regarding this error, but I am not being successful implementing a solution. Any tips on how I should proceed?
Thanks guys!
Ranges are more complicated and simpler at the same time in swift.
You want subdata(in: start..<end), which makes a Range<Int>, which is the type you need. However, in this case start and end refer to the beginning and end indexes of the range, not the location and length as you pass to NSMakeRange().
As #jrturton already said, subdata(in:) takes a Range<Int> argument,
so it should be
originalEntitlementsData = originalEntitlementsData.subdata(in: i..<i+end)
in your case. But note that all the conversions to NSData, taking
the .bytes, explicit loop and memcmp are not needed if you
take advantage of the existing range(of:) method of Data:
var originalEntitlementsData = entitlementData
let xmlStart = "<?xml".data(using: .utf8)!
if let range = originalEntitlementsData.range(of: xmlStart) {
originalEntitlementsData = originalEntitlementsData.subdata(in: range.lowerBound..<originalEntitlementsData.endIndex)
// Alternatively:
// originalEntitlementsData.removeSubrange(0..<range.lowerBound)
}

MDQueryGetResultAtIndex and UnsafeRawPointer in Swift 3

I'm having trouble exploring the results of a Spotlight search in Swift 3 using MDQuery. I expect MDQueryGetResultAtIndex to yield an MDItem, and in C/Objective-C, that assumption works, and I can call MDItemCopyAttribute on it to explore the item. Here, for example, I successfully get the pathname of a found item:
MDItemRef item = (MDItemRef)MDQueryGetResultAtIndex(q,i);
CFStringRef path = MDItemCopyAttribute(item,kMDItemPath);
But in Swift 3, MDQueryGetResultAtIndex returns an UnsafeRawPointer! (it's a pointer-to-void in C). To get past that, I've tried, for example:
if let item = MDQueryGetResultAtIndex(q, 0) {
let ptr = item.bindMemory(to: MDItem.self, capacity: 1)
let path = MDItemCopyAttribute(ptr.pointee, kMDItemPath)
}
but that crashes, and logging shows that ptr.pointee is an NSAtom. It is quite evident that my personal UnsafeRawPointer mojo is not working (and to be frank I've always found this confusing).
How would I transform this UnsafeRawPointer into something I can successfully call MDItemCopyAttribute on?
Alternatives
I can get over this hump by putting my Objective-C code into an Objective-C helper object and calling it from Swift; but I'd like to write a pure Swift solution.
Similarly, I could probably rewrite my code to use the higher-level NSMetadataQuery, and I may well do so; but my original Objective-C code using the lower-level MDQueryRef works fine, so now I'm curious how to turn it directly into Swift.
Complete code for those who would like to try this at home:
let s = "kMDItemDisplayName == \"test\"" // you probably have one somewhere
let q = MDQueryCreate(nil, s as CFString, nil, nil)
MDQueryExecute(q, CFOptionFlags(kMDQuerySynchronous.rawValue))
let ct = MDQueryGetResultCount(q)
if ct > 0 {
if let item = MDQueryGetResultAtIndex(q, 0) {
// ...
}
}
The problem in your code
if let item = MDQueryGetResultAtIndex(q, 0) {
let ptr = item.bindMemory(to: MDItem.self, capacity: 1)
let path = MDItemCopyAttribute(ptr.pointee, kMDItemPath)
}
is that the UnsafeRawPointer is interpreted as a pointer to an
MDItem reference and then dereferenced in ptr.pointee, but
the raw pointer is the MDItem reference, so it is dereferenced
once too often.
The "shortest" method to convert the raw pointer to an MDItem reference
is unsafeBitCast:
let item = unsafeBitCast(rawPtr, to: MDItem.self)
which is the direct analogue of an (Objective-)C cast.
You can also use the Unmanaged methods
to convert the raw pointer to an unmanaged reference and from there
to a managed reference (compare How to cast self to UnsafeMutablePointer<Void> type in swift):
let item = Unmanaged<MDItem>.fromOpaque(rawPtr).takeUnretainedValue()
This looks a bit more complicated but perhaps expresses the intention
more clearly. The latter approach works also with (+1) retained
references (using takeRetainedValue()).
Self-contained example:
import CoreServices
let queryString = "kMDItemContentType = com.apple.application-bundle"
if let query = MDQueryCreate(kCFAllocatorDefault, queryString as CFString, nil, nil) {
MDQueryExecute(query, CFOptionFlags(kMDQuerySynchronous.rawValue))
for i in 0..<MDQueryGetResultCount(query) {
if let rawPtr = MDQueryGetResultAtIndex(query, i) {
let item = Unmanaged<MDItem>.fromOpaque(rawPtr).takeUnretainedValue()
if let path = MDItemCopyAttribute(item, kMDItemPath) as? String {
print(path)
}
}
}
}

Interpolate String Loaded From File

I can't figure out how to load a string from a file and have variables referenced in that string be interpolated.
Let's say a text file at filePath that has these contents:
Hello there, \(name)!
I can load this file into a string with:
let string = String.stringWithContentsOfFile(filePath, encoding: NSUTF8StringEncoding, error: nil)!
In my class, I have loaded a name in: let name = "George"
I'd like this new string to interpolate the \(name) using my constant, so that its value is Hello there, George!. (In reality the text file is a much larger template with lots of strings that need to be swapped in.)
I see String has a convertFromStringInterpolation method but I can't figure out if that's the right way to do this. Does anyone have any ideas?
This cannot be done as you intend, because it goes against type safety at compile time (the compiler cannot check type safety on the variables that you are trying to refer to on the string file).
As a workaround, you can manually define a replacement table, as follows:
// Extend String to conform to the Printable protocol
extension String: Printable
{
public var description: String { return self }
}
var string = "Hello there, [firstName] [lastName]. You are [height]cm tall and [age] years old!"
let firstName = "John"
let lastName = "Appleseed"
let age = 33
let height = 1.74
let tokenTable: [String: Printable] = [
"[firstName]": firstName,
"[lastName]": lastName,
"[age]": age,
"[height]": height]
for (token, value) in tokenTable
{
string = string.stringByReplacingOccurrencesOfString(token, withString: value.description)
}
println(string)
// Prints: "Hello there, John Appleseed. You are 1.74cm tall and 33 years old!"
You can store entities of any type as the values of tokenTable, as long as they conform to the Printable protocol.
To automate things further, you could define the tokenTable constant in a separate Swift file, and auto-generate that file by using a separate script to extract the tokens from your string-containing file.
Note that this approach will probably be quite inefficient with very large string files (but not much more inefficient than reading the whole string into memory on the first place). If that is a problem, consider processing the string file in a buffered way.
There is no built in mechanism for doing this, you will have to create your own.
Here is an example of a VERY rudimentary version:
var values = [
"name": "George"
]
var textFromFile = "Hello there, <name>!"
var parts = split(textFromFile, {$0 == "<" || $0 == ">"}, maxSplit: 10, allowEmptySlices: true)
var output = ""
for index in 0 ..< parts.count {
if index % 2 == 0 {
// If it is even, it is not a variable
output += parts[index]
}
else {
// If it is odd, it is a variable so look it up
if let value = values[parts[index]] {
output += value
}
else {
output += "NOT_FOUND"
}
}
}
println(output) // "Hello there, George!"
Depending on your use case, you will probably have to make this much more robust.