Calling Swift Code from C++

The title says it all. Is there a way to call swift code directly in C++? While I do enjoy working in swift, I have written an entire compiler in C++, and wish to rush it in swift.. I have read somewhere that swift's calling conventions differ slightly from C, and C++ name mangling further complicates the discussion. That being said, is this impossible? Or is there a solution?

As far as I am aware, Swift calling conventions differ from C quite a lot. I am not a skilled C++ developer, but assuming that methods callable from C will be also callable from C++, there are two solutions I am aware of.

First solution is described in C reverse interoperabiity thread. Simply put @_cdecl("foo") annotation before your public swift function in global scope. I also assume, that the _ character implies, that cdecl annotation is not specified in Swift but merely a feature of the compiler which may not work at every instance, should be used with caution and may be subject to changes.

Also notice, that Swift's types, even the "simple" ones like structs, are not guaranteed to use C layout, so C structs can be used from Swift but Swift struct may not work in C.

Second solution is a Swift feature. You can create closure with @convention(c) annotation. Such closure is safe to pass as a function pointer to C function. The tradeoff is, that capture lists are not available in such closures. Attributes — The Swift Programming Language (Swift 5.7)

Hope this helps.

3 Likes

I also found this interesting thread on the topic :) Best way to call a Swift function from C? - #14 by Krischu

1 Like

First, while it looks Swift will have a great support for C++ in the near future, for now you will need to create simple C wrappers to your interfaces.

I will tell you what i do here:

I register a callback in swift that is a c callback, but always pass a 'state' option to the C function.

like this:

I define a callback structure in C like

(thing.h) C++: suppose you have this class

class Thing { 
 public:  
   bool A(int x, int y); 
   bool B(int x, int y, int z); 
   bool C(int x);
};

thing_shims.h:

typedef void* CThing;

typedef struct {
   int(*callA)(void* state, int x, int y);
   int(*callB)(void* state, int x, int y, int z);
} ThingCallbacks;

CThing _CThingCreate(void*, ThingCallbacks callbacks);
void _CThingDestroy(CThing);
int _CThingC(CThing, int x);

in Swift:

import CThingShims // this is the native shim C library

// NOTE: you will need to use a class not a struct, giving you will use its raw pointer
// as the state that C will pass it over back to you

Class Thing {  

 // this is the wrapper created on C++ size.. unfortunatelly it needs to be heap allocated 
 // we can manage its lifetime bounded with the lifetime of this class by using deinit {}
 // to destroy the heap allocated wrapper 
 let reference: CThing

init() {
    var callbacks = ThingCallbacks()
    // zero out the struct 
    memset(&callbacks, 0, MemoryLayout<ThingCallbacks>.stride)

   // now here is the trick

    callbacks.callA = { 
        (handle: UnsafeMutableRawPointer?, 
        x: CInt, 
        y: CInt)  -> CInt in 
         
        let this = unsafeBitCast(handle, to: Thing.self)
        return this.A(x: Int(x), y: Int(y)) ? 1 : 0
    }

    callbacks.callB = { 
        (handle: UnsafeMutableRawPointer?, 
        x: CInt, 
        y: CInt,
        z: CInt,) -> CInt in 
         
        let this = unsafeBitCast(handle, to: Thing.self)
        return this.B(x: Int(x), y: Int(y), z: Int(z)) ? 1 : 0
    }    
    let this = unsafeBitCast(Unmanaged.passUnretained(self).takeUnretainedValue(), to: UnsafeMutableRawPointer.self)
    reference = _CThingCreate(this, callbacks)  
  }

  deinit {
    _CThingDestroy(reference)
  }

  func A(x: Int, y: Int) -> Bool {
     // do your swift thing here
     return doA()
  }

 func B(x: Int, y: Int, z: Int) -> Bool {
    // do your swift thing here
    return doB()
  }

  func C(x: Int) -> Bool {
    return _CThingC(reference, CInt(x)) != 0
  }

}

now on C++ (thing.cc or thing.cpp file):

class ThingWrapper : public mycode::InterfaceCppWillCall {
 public: 
   ThingWrapper(Thing* real_thing, void* state, ThingCallbacks callbacks):
    real_thing_(real_thing), 
    state_(state), 
    callbacks_(callbacks) {
   real_thing_->register(this);
 }

  // here your C++ code will call (A and B on Swift)
  bool A(int x, int y) override {
     return callbacks_.callA(state, x, y) != 0;
  }
  bool B() override {
    return callbacks_.callB(state, x, y, z) != 0;
  }
  // here the Swift code will call C++
  bool C() {
     return real_thing_->C();
  }
private:
 Thing* real_thing_;
 void* state_;
 ThingCallbacks callbacks_;
};

CThing _CThingCreate(void* state, ThingCallbacks callbacks) {
  // Get Thing here somehow
  Thing* real_thing = ...
  return new ThingWrapper(real_thing, state, callbacks);
}
void _CThingDestroy(CThing handle) {
  reinterpret_cast<ThingWrapper *>(handle);
}

int _CThingC(CThing, int x) {
  return reinterpret_cast<ThingWrapper *>(handle)->C() ? 1 : 0;
}

Note that on your C++ code you create the mycode::InterfaceCppWillCall {} interface so your C++ code needs to only use that as a reference.

class InterfaceCppWillCall {
   public:
     virtual bool A(int x, int y) = 0;
      ...
 };

class Thing {
  public:
   // a method or in the constructor if you can
    void register(InterfaceCppWillCall* interface) {
      interface_ = interface;
    }

    bool A(int x, int y) { return interface_->A(x, y); }
    ...
  private:
    InterfaceCppWillCall* interface_;
};

Its a little work, but its doable.

I hope it helps.

You can use Scapix to call C++ from Swift (and the other way around using callbacks). Scapix automatically generates ObjC/Swift bindings directly from C++ headers, as part of the build.

Example project with C++ library and Swift UI: GitHub - scapix-com/example1: Example for Scapix Language Bridge

When C++ header changes, Scapix automatically generates updated ObjC/Swift bindings during build.

To call Swift from C++, use C++ std::function parameter as callback.

Disclamer: I am the author of Scapix Language Bridge.

Thanks everyone. I am able to call swift library functions from my C app using this method:

Swift:
@_silgen_name("write_native")
public func write_native(buf:UnsafePointer, length: CUnsignedInt, endpt: Int) -> Int {
var array = UInt8
for i in 0...(length-1) {
array.append(UInt8(buf[Int(i)]))
}

WriteToBle(buffer: array, endpt: endpt, id: 1)
return 0

}

func WriteToBle(buffer: [UInt8], endpt: Int, id: Int) {
let idx = GetDevice(id: id)
if (idx != -1)
{
doWrite_withoutResponse(buffer: buffer, endpt: endpt, id: idx);
}
}

func doWrite_withoutResponse(buffer: [UInt8], endpt: Int, id: Int) {
let dp = Data(buffer)
_periph?.writeValue(dp, for: Characteristic!, type: .withResponse)
}

C:
extern int write_native(const uint8_t* buf, uint32_t length, int endpt);
int dfu_write(const uint8_t* buf, uint32_t length, int endpt)
{
int ret = write_native(buf, length, endpt);
return ret;
}

I am getting the following memory error sometimes. I am writing some data to a BLE device in the swift code. Does anyone know what is wrong?

Console output:
bletest(65982,0x10d3875c0) malloc: Incorrect checksum for freed object 0x7ffa5640f518: probably modified after being freed.
Corrupt value: 0x7ffa5640fcc
bletest(65982,0x10d3875c0) malloc: *** set a breakpoint in malloc_error_break to debug

Stack:
__pthread_kill 0x00007fff741342c2
pthread_kill 0x00007fff741efbf1
abort 0x00007fff7409e6a6
malloc_vreport 0x00007fff741ad077
malloc_zone_error 0x00007fff741c5e0d
tiny_free_list_remove_ptr 0x00007fff741a984b
tiny_free_no_lock 0x00007fff741a721c
free_tiny 0x00007fff741a6d79
_swift_release_dealloc 0x00007fff73a11a60
$s9SwiftCode14C23doWrite_withoutResponse6buffer5endpt2idySays5UInt8VG_S2itF 0x000000010ca0b5cb
$s9SwiftCode14C11WriteToBle6buffer5endpt2idySays5UInt8VG_S2itF 0x000000010ca0b0e1
write_native 0x000000010ca0fd02
main 0x000000010c9d122d
start 0x00007fff73ff93d5

I believe you should use @_cdecl rather than @_silgen_name, as the latter uses the Swift calling convention.

2 Likes

Thanks, I made this change. The malloc problem was due to a buffer being modified that wasn't declared mutable so it is working now.