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.

4
  • 3
    Poor person who is going to maintain that code. (I'd call such code "one-way/disposable code".) Commented Jul 12, 2016 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". Commented Jul 12, 2016 at 13:04
  • I did not comment on contents, just on coding style. That massively indented part cannot be meant seroiusly in any PL. Commented Jul 12, 2016 at 13:05
  • I would love to know how to use those pointers without the crazy tree of nested withUnsafeMutableBufferPointers. The only "solution" I've found is to write a wrapper for that tree (which means it still exists somewhere). Commented Jul 12, 2016 at 13:08

2 Answers 2

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) }
}
Sign up to request clarification or add additional context in comments.

12 Comments

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)
(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.
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 stackoverflow.com/questions/30786883/… for an example.
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.
@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?
|
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.

6 Comments

I do know that (it's a standard feature of many languages). I fail to see how it helps here.
And that's already how I'm passing all of the arrays for the inout parameters.
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.
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.
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.