4

I'm writing a wrapper around a C mathematical library. Every function takes one or two functions as arguments. However, the arguments for those child functions (as well as the parent functions) are not Swifty -hence the wrapper.

I've cleaned up the example code to just show the three main pieces: the c-library function, the desired Swift function that would be passed to the wrapper (body not shown, but wrapping around the c-library function), and the required C function form.

//C library function, that calls the passed function dozens, hundreds or thousands of times, each time it changes the data provided in p, and uses the output from x
//The Swift arrays are passed as pointers, and the length of the and x array are m and n respectively
returnValue = cLibraryFunc(passedFunc, &p, &x, Int32(m), Int32(n), Int32(itmax), &opts, &info, &work, &covar, &adata)


//I would like to create a Swift function that would look like this (internals could be any myriad of things that takes inputs p and adata and returns data in x:
func desiredSwifty(p: inout [Double], x: inout [Double], m: Int, n: Int, adata: inout [Double]) {
    //very simple example
    //this example knows the length of p (so m as well)
    //and assumes that adata length is the same as the x length (n)
    //obviously, it could ifer m and n from p.count and x.count

    for i in 0..<n {
        x[i] = p[0] + p[1]*adata[i]  + p[2]*pow(adata[i], 2)
    }
}


//And the wrapper would "convert" it -internally- into the form that the C library function requires:
func requiredC(p: UnsafeMutablePointer<Double>?, x: UnsafeMutablePointer<Double>?, m: Int32, n: Int32, adata: UnsafeMutablePointer<Void>?) {
    //same thing, but using pointers, and uglier

    //first, have to bitcast the void back to a double
    let adataDouble : UnsafeMutablePointer<Double> = unsafeBitCast(adata, to: UnsafeMutablePointer<Double>.self)

    for i in 0..<Int(n) {
        x![i] = p![0] + p![1]*adataDouble[i]  + p![2]*pow(adataDouble[i], 2)
    }
}

addition

I should add that I have access to the c source code, so I could possibly add some dummy parameters (possibly to find a way to pass context in). But given that the docs seem to indicate that one can't grab context with a c function pointer, this may be of no use.

Adam Jones
  • 775
  • 6
  • 23
  • 3
    Poor person who is going to maintain that code. (I'd call such code "one-way/disposable code".) – too honest for this site Jul 12 '16 at 13:01
  • I've already done some testing on the C library, and since it's well documented and used by others, I feel safe with the current version. However, I only plan to use this wrapper for myself. I just want to hide away the C "ugliness". – Adam Jones Jul 12 '16 at 13:04
  • I did not comment on contents, just on coding style. That massively indented part cannot be meant seroiusly in any PL. – too honest for this site Jul 12 '16 at 13:05
  • I would love to know how to use those pointers without the crazy tree of nested `withUnsafeMutableBufferPointer`s. The only "solution" I've found is to write a wrapper for that tree (which means it still exists somewhere). – Adam Jones Jul 12 '16 at 13:08

2 Answers2

5

(Note: the following example uses Swift 3 on Xcode 8 beta 2.)

Your question is about C functions taking another C function as an argument, so let us reduce the question to that problem. Here is a simple C function which takes a single argument which is again a C function which takes a pointer to an array of doubles and an integer count:

// cfunction.h:
void cFunc(void (*func)(double *values, int count));

// cfunction.c:
void cFunc(void (*func)(double *values, int count)) {
    double x[] = { 1.2, 3.4, 5,6 };
    func(x, 3);
}

This function is imported to Swift as

func cFunc(_ func: (@convention(c) (UnsafeMutablePointer<Double>?, Int32) -> Swift.Void)!)

Here @convention(c) declares the block to have C-style calling conventions. In particular, from Swift you can pass only a global function or a closure which does not capture any context.

A simple example for a Swift wrapper is

func swiftyFunc(passedFunc: (@convention(c) (UnsafeMutablePointer<Double>?, Int32) -> Void)) {
    cFunc(passedFunc) 
}

which you can use like this:

func functionToPass(values: UnsafeMutablePointer<Double>?, count: Int32) {
    let bufPtr = UnsafeBufferPointer(start: values, count: Int(count))
    for elem in bufPtr { print(elem) }
}

swiftyFunc(passedFunc: functionToPass)

or with a closure argument:

swiftyFunc { (values, count) in
    let bufPtr = UnsafeBufferPointer(start: values, count: Int(count))
    for elem in bufPtr { print(elem) }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I am using Swift 3. I was looking at your answer (just after I thought I had figured it out), but the complier errors, and your answer both lead me to the same issue. If I can't capture context, then how can I work it. Your example is the exact opposite of the wrapper I (think I) need. I think that I need a cFunc wrapping a swiftyFunc (and that swiftyFunc is what is passed as an argument) – Adam Jones Jul 12 '16 at 16:49
  • (network issue). I think that I need a cFunc that wraps the swiftyFunc that is passed in to the main wrapper. And then that cFunc is what is passed to the c library function that I'm wrapping. – Adam Jones Jul 12 '16 at 16:56
  • I have taken the names from your question. `swiftyFunc` is the wrapper. It passes the Swift function `functionToPass` to `cFunc` which in turn calls `functionToPass` with some data. Perhaps I misunderstood the problem? – You cannot pass closures with capture context to a C function expecting a function argument. The only way is to pass context in form of a `void *userData` pointer around, see http://stackoverflow.com/questions/30786883/swift-2-unsafemutablepointervoid-to-object for an example. – Martin R Jul 12 '16 at 17:03
  • You have used the same names. However, my intention for making a swift wrapper was to avoid using the pointer types as function arguments. I would like the Swift function that I pass to the wrapper to be using the "clean" arguments that I show in the wrapper function declaration. And I was hoping to be able to "mangle"/"convert"/whatever that passed function into the format that I show with the mutablepointers. That way, all of the conversion happens inside the framework I'm building, with none of the "nasty" arguments exposed to my consumer code. – Adam Jones Jul 12 '16 at 17:25
  • @AdamJones: That should be possible, but not without *copying* the data from a C array to a Swift array and back. – Does the C function allocate the array? Does the callback only read the contents or modify it? – Martin R Jul 12 '16 at 17:32
  • The overhead on copying would be too great, given how frequently the function will be called by the c library function. – Adam Jones Jul 12 '16 at 17:36
  • @AdamJones: Would passing an UnsafeMutableBufferPointer be a compromise? That has subscript accessors like an array, but does not copy the data. – Martin R Jul 12 '16 at 17:37
  • I'll answer your modified question first. The swift/c function will modify the data based on the inputs. – Adam Jones Jul 12 '16 at 17:40
  • I think the unsafemutablebufferpoint is basically the same as using the pointers already required - in that it looks unswiftly and requires more thought in writing the "consumer" function that will be passed to the wrapper. – Adam Jones Jul 12 '16 at 17:43
  • @AdamJones: Then I don't have a solution for you, sorry.. A (mutable) Swift `Array` cannot be created from a `double *` pointer without copying the data. – Martin R Jul 12 '16 at 17:50
  • Hmm. You may be thinking along the same lines I was testing (but my idea required scope). Are you thinking of "creating" (really just re-casting pointers) inside the wrapper function - while allowing the functions to keep the desired argument list above (at bottom of the question)? – Adam Jones Jul 12 '16 at 17:52
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/117124/discussion-between-martin-r-and-adam-jones). – Martin R Jul 12 '16 at 17:54
0

Do you know that you can get a mutable pointer to a var just by using the & operator? It does the "right thing" on arrays too.

func foo(_ x: UnsafeMutablePointer<Int>) {
    print(x)
}

func bar(_ x: UnsafeMutablePointer<Int>) {
    print(x)
}

var array = [0]
foo(&array)

var int = 0
bar(&int)

(Tested on Swift 2, but most likely still valid on Swift 3.)

I suspect that this could drastically reduce your need for wrappers.

zneak
  • 134,922
  • 42
  • 253
  • 328
  • I do know that (it's a standard feature of many languages). I fail to see how it helps here. – Adam Jones Jul 12 '16 at 20:52
  • And that's already how I'm passing all of the arrays for the inout parameters. – Adam Jones Jul 12 '16 at 21:00
  • That is not what your question shows, at the very least. More than half of the swiftyFunc lines are calls to `withUnsafeMutableBufferPointer`. You can accuse me of drawing hasty conclusions, but this really obscures what you want to do. – zneak Jul 12 '16 at 21:03
  • Once I had some time, I decided to go back and look at the docs, the Swift definitions, and a ton of documentation on the web. I have some other large arrays in my code that are constantly appended to, and I've written a fair amount of code to queue their operations. So, when I wrote some wrappers for Accelerate functions, I wanted to make sure that the pointer stayed valid for the length of the call, and `withUnsafeBufferPointer` was what I came across (and I see other wrapper libraries do the same thing). However, yes, it does compile with just the ampersands. – Adam Jones Jul 13 '16 at 04:38
  • I guess in the case of a value-type array, the Apple docs are very ambiguous about what is happening under the hood. They suggest either Array or ContiguousArray, and `withUnsafeMutableBufferPointer` it says it makes sure a block of contiguous memory is pointed to. However, nothing is said regarding the use of `&`. Maybe my code is being over-protective, but I can't find any assurances in the docs that it isn't. Any pointers here (pun intended) would be helpful. – Adam Jones Jul 13 '16 at 04:42
  • @AdamJones, the operation is internally called "ArrayToPointer". The [expression class's documentation](https://github.com/apple/swift/blob/0a1275040fd228cebbbcd594b9ad54c095532287/include/swift/AST/Expr.h#L2273) is simply "Convert the address of an array to a pointer." [This test](https://github.com/apple/swift/blob/5374081c41fd21fcec35605db40dc806a9d52250/test/SILGen/pointer_conversion.swift) verifies that the operation compiles. You can verify that `ArrayToPointer` is indeed that by using `swiftc --dump-ast` on a file that uses it (and look for `array_to_pointer` in the output). – zneak Jul 13 '16 at 04:59