Call swift function by pointer

Basically, the problem is to call swift function by its pointer, when pointer is coming from some code. There is no way to just cast it to required type, as, by default, type of opaque swift function has size for two pointers, function itself, and context. I've found two ways to work around this:

// Functions with known pointers
public func sayHi(to: String) {
  print("Hi, \(to)")
}

@_cdecl("sayhi_cdecl")
public func sayHiCdecl(to: UnsafePointer<CChar>) {
  print("Hi, \(String(cString: to))")
}

// Code to invoke this functions by pointer
func invoke(thinPtr: UnsafeRawPointer, cPtr: UnsafeRawPointer) {
  // corresponds to sayHi
  let thin = unsafeBitCast(
    thinPtr, to: (@convention(thin) (String) -> ()).self)
  // corresponds to sayHiCdecl
  let c = unsafeBitCast(
    cPtr, to: (@convention(c) (UnsafePointer<CChar>) -> ()).self)

  thin("John Appleseed")
  c("John appleseed")
}

Both of this approaches are working, but not very well documented, cdecl has more occurrences in tests and different papers, but it starts with underscore, which means it is not intended to use by developers. @convention(thin) in the other side doesn't start with underscore, but it has very few occurrences in swift code (not considered sil), and the only place I've found it in docs documents sil-level convention attributes with remark

This is similar to the language-level @convention attribute, though SIL extends the set of supported conventions with additional distinctions not exposed at the language level

So, is there any other, not so "undergroud" way of doing this? If not, what approach is preferable?

@convention(thin) ought to be underscored too. It doesn't work outside of very limited circumstances and will miscompile your code. Use @_cdecl to declare functions you want to be able to link to and call from C, and use @convention(c) function types if you need a C-compatible function pointer. To form C function pointers from C, you don't need to unsafeBitCast. Assigning a function to a variable of the appropriate type, or passing it as an argument, will produce a pointer with the correct convention:

func foo() {}

let f: @convention(c) () -> Void = foo

You can use unsafeBitCast to recover C function pointers from Unsafe*Pointer. Any pointer to a Swift symbol you got from C, by calling dlsym, or referencing a mangled Swift symbol name from C, however cannot be used as-is as a function value, and it's not a good idea to bitcast those. Use @_cdecl to export symbols that can be used as C function pointers.

Thank you for some clarification!

Hmm, interesting. Isn't it exactly how mirrors work with swiftcall attribute? I had a feeling that @convention(thin) is the same as __attribute__((swiftcall)) but on swift side :slight_smile:

The Swift runtime is compiled using a custom Clang that knows some things about the Swift calling convention, and even with that, has to be written very carefully in sync with the compiler implementation. You can't replicate that behavior with a stock C or C++ compiler.

Terms of Service

Privacy Policy

Cookie Policy