(c-interop) how to manually set up an instance method call?

I’m trying to register some Swift types with a C API (Godot Nativescript) so that the C framework can call instance methods on the Swift objects.

The registration function provided by the C API allows you to specify a callback function, and a method-specific user pointer. The callback function receives an instance-specific user pointer, and has to be C-callable, so I’m using something like

{
    (
        method:UnsafeMutableRawPointer?, 
        instance:UnsafeMutableRawPointer?, 
        count:Int, // number of arguments 
        arguments:UnsafeMutableRawPointer? // function arguments
    ) in 

    // some stuff with `Unmanaged` to reconstitute `self`
    // ...
    object = Unmanaged<CCallableObject>.fromOpaque(instance).takeRetained()
    // ...
    // need to do this: 
    object.{SWIFT METHOD}(count, arguments)
}

For registered properties, the obvious thing to do here would be to send a KeyPath to the C API, in the form of the method-specific pointer, and use that to set up the property access/modify. But that doesn’t work with instance methods, so I’m guessing the workaround is going to have to involve some manual hacking with the runtime to set up this function call. In which case, how can this be achieved?

(mods: putting this in the Compiler section since this is probably related to a lot of low-level runtime details, but feel free to move this to the Using Swift section)

You could make your instance method an AnyObject and then get an opaque pointer from it.

Proof that it works:

For

class Foo { func bar(_ x: String) throws -> [Int] { return [x.count] } }

You could

  1> class Foo { func bar(_ x: String) throws -> [Int] { return [x.count] } }
  2> Unmanaged.passRetained(Foo.bar as AnyObject).toOpaque()
$R0: UnsafeMutableRawPointer = {
  _rawValue = 0x0000000100504280
}

So we converted Foo.bar into 0x0000000100504280. And this is how we can retrieve (and call it):

  3> (Unmanaged<AnyObject>
            .fromOpaque(UnsafeRawPointer(bitPattern: 0x0000000100504280)!)
            .takeUnretainedValue() as! ((Foo) -> (String) throws -> [Int]))
            (Foo())("hello")
$R1: [Int] = 1 value {
  [0] = 5
}

Which correctly invokes Foo().bar("hello") returning [5].

Because takeUnretainedValue won't consume a reference count, you can invoke the function this was as many times as you want. You will need to release() / takeRetainedValue() it exactly once to correctly deallocate all resources though.

1 Like

This almost works but the problem is the cast to ((Foo) -> (String) throws -> [Int], where Foo becomes generic when this is implemented as an extension on the protocol CCallableObject.

In Swift-facing code this isn’t a problem, because Self serves this purpose, but functions that capture Self aren’t C-callable.

Not sure I 100% understand what you need but you can make self an Any too, is that what you need? Eg

class Foo { func bar(_ x: String) throws -> [Int] { return [x.count] } }

func anyliseFun<S, R>(_ fun: @escaping (S) -> R) -> (Any) -> R {
    return { any in
        return fun(any as! S)
    }
}

let retainedInvoker = Unmanaged.passRetained(anyliseFun(Foo.bar) as AnyObject).toOpaque()

let result = try (Unmanaged<AnyObject>
    .fromOpaque(UnsafeRawPointer(bitPattern: UInt(bitPattern: retainedInvoker))!)
            .takeUnretainedValue() as! ((Any) -> (String) throws -> [Int]))(Foo())("hello")
1 Like

this might actually work, I didn’t think of generating functions to perform the cast to S. thanks!

1 Like