Calling standalone C++ function from Swift

I'm finding that I can call standalone C functions from Swift without any problem.
But when I try to call a standalone C++ function, the linker claims the function doesn't exist.

e.g. calling from Swift:
fubar (123); // error: undefined reference to fubar

e.g. in source.cpp:
static std::vector<int> myvector;
void fubar (int x) {
    myvector.push_back(x);
}

Does anyone know why a standalone C++ function wouldn't link properly but a C function would?

It also seems that bridging via Objective-C isn't working:

bridging.h:16:9: note: in file included from bridging.h:16:
#import "Bridge.h"
        ^
./Bridge.h:2:1: error: expected identifier or '('
@interface Bridge
^
bridging.h:16:9: note: in file included from bridging.h:16:
#import "Bridge.h"

Check that you are exporting the C++ function as a C function. (C++ function signatures are mangled; Swift can't see them unless they are exported as C functions.)

// Fubar.h

#ifdef __cplusplus
extern "C" {
#endif

void fubar (int);
void fubar_print ();

#ifdef __cplusplus
}
#endif
// Fubar.cc

#include "Fubar.h"

#include <iostream>
#include <vector>

using MyVector = std::vector <int>;
static MyVector myvector;

#ifdef __cplusplus
extern "C" {
#endif

void fubar (int x) {
   std::cout << __PRETTY_FUNCTION__ << ": " << x << std::endl;
   myvector.push_back(x);
}

void fubar_print () {
    std::cout << __PRETTY_FUNCTION__ << "..." << std::endl;
    std::copy (myvector.begin(), myvector.end(), std::ostream_iterator <MyVector::value_type> (std::cout, " "));
    std::cout << std::endl;
}

#ifdef __cplusplus
}
#endif

// Bridging-Header.h

#include "Fubar.h"
// T1.swift

@main
struct T1 {
    static func main () {
        fubar (2)
        fubar (3)
        fubar (5)
        fubar (7)
        fubar_print ()
    }
}
void fubar(int): 2
void fubar(int): 3
void fubar(int): 5
void fubar(int): 7
void fubar_print()...
2 3 5 7 

What I've found works (for now) is to have two header files for my C++ file.

  • myfile-c.h is what I can #include in the bridge.h file
  • myfile-cpp.h is what I can #include in my C++ code

It seems that swiftc is reallly sensitive to what is in bridge.h... it can't be C++ or Obj-C. It has to be C.

It's also very easy to cause clang to complain that the language model differs for a particular function from one file to another.

Does your header forward declare fubar? Your header needs to either forward declare fobar or include a header that does.

What's your project setup look like? Are you using Xcode? Are you on Linux? Are you passing -import-objc-header?

What's your end goal here? Do you want to call C++ functions? Do you want to use Objective-C? (If you're on macOS you can do both.)

Yes and yes. I want C++ functions.

I'm also using classes but I've been assuming the best way to build a bridge between Swift and C++ is to use standalone functions.

Also my header does declare fubar, however because swiftc refuses to parse any C++ in the bridging header, I have to have two headers, one for swiftc and one for C++ i.e. clang.

I've found that if I segregate C functions with "extern C" within a C++ source file, Swift can call them just fine.

But this idea of putting "extern C" inside of the ifdef cplusplus seems odd. Does that imply that swiftc will scan those C++ files for the "extern C"?

When I use that ifdef+externC in my C++, I get this error:
myfile.cpp:118:5: error: declaration of 'myFunction' has a different language linkage

Thus, it appears the only maintainable way to call C++ from Swift is to use C functions (in the C++ file) as an intermediate.

Add -cxx-interoperability-mode=default and you'll be able to use C++ functions, classes, etc.

Both swiftc and clang are saying that's an unknown argument. I'm using Swift version 5.9-dev.

$ strings `which swiftc` | grep interop | grep cxx
-cxx-interop-getters-setters-as-properties
-enable-experimental-cxx-interop-in-clang-header
-enable-experimental-cxx-interop

When I use -enable-experimental-cxx-interop, and I add a C++ function to the bridging header, I get:

./bridging.h:25:10: error: declaration of 'myCPPfunc' has a different language linkage

So maybe development of Swift for Linux is a little behind Swift for MacOS?

./bridging.h:25:10: error: declaration of 'myCPPfunc' has a different language linkage

Do you still have an extern C block somewhere?

You can upgrade the compiler if you want the new flag. Otherwise make sure it's -Xfrontend -enable-experimental-cxx-interop.

Yes, basically in my C++ file that I need to call from Swift, I have the extern C block with C code in it that can call the C++ code in the same file.

The -Xfrontend -enable-experimental-cxx-interop option just seems to give me the same errors.

I suspect the error you're running into is that the declaration is extern C, while the definition is not. This doesn't haven anything to do with Swift. Just remove the extern C block, and let it be a normal, C++ forward declaration. Swift can handle those when you pass the enable interop flag.

// Header
void fubar (int x);

// C++ file
static std::vector<int> myvector;
void fubar (int x) {
    myvector.push_back(x);
}

[/quote]

Swift should be able to understand that fine (without any extern C).

No luck. I added it to the bridge header so that Swift knew about it. I didn't use extern "C". I compiled it and linked with its .o file. And yet...

/tmp/TemporaryDirectory.OwUCQF/app-1.o:app-1.o:function $s1m5AppCACycfc: error: undefined reference to 'fubar'

I suspect you either need to recompile the c++ object file or maybe you're liking them together wrong. Seems like you got the hard part figured out though!

You just have to consider all these factors in concert:

  1. Bridging header shall be using C / Obj-C language. Either no C++ stuff there or enclose C++ bits in "#ifdef __cplusplus" brackets.
  2. Normally you won't need to use this bridging header in C++, but if you do remember that C++ doesn't know Obj-C, so you'll need some brackets there as well.
  3. There's a language mode called Obj-C++, typically denoted via ".mm" file extension - this language mode knows all three languages.
  4. "foobar" (used in Swift) should have C linkage. This linkage is automatic in C / Obj-C, just in case of C++ you have to tell this explicitly.
  5. The easiest thing to tell this in C++ header (and make that header compatible with C/Swift) is to use the "#ifdef __cplusplus" brackets:
#ifdef __cplusplus
extern "C" {
#endif

... stuff here that should have C linkage
void foobar(int);

#ifdef __cplusplus
}
#endif

If it was a header purely for C++ usage you would be able specifying the C linkage more easily:

extern "C" void foobar(int);

But that's not compatible with C / Swift, so to use the same header in there you'd still need brackets:

#ifdef __cplusplus
extern "C" {
#endif

extern "C"

#ifdef __cplusplus
}
#endif

void foobar(int);

Which is less nice than the above brackets especially if you need more than one declaration.

  1. You may have two different headers:
// Bridging header
void foobar(int);

//C++ header:
extern "C" foobar(int);

But then you'd need to remember to keep them in sync. I'd not go that route but understandably for some that way would be easier to cope with ("no those ugly looking #ifdefs")

I'm on Linux, so my clang doesn't have Foundation. I think this might limit my ability to use Obj-C or Obj-C++ effectively.
So far the C-calling-C++ approach is working.
What would be more convenient though would be if I could create a global C++ object that I could call from Swift.

1 Like

I believe for now you either have to call "Swift -> C -> C++" or "Swift -> Obj-C -> C++", creating the intermediate wrapper above your C++ object in C or Obj-C. The C / Obj-C wrapper part could still live in the same C++ / Obj-C++ file if that's more convenient for you. Don't know whether this is any different on Linux. :man_shrugging:

I can't really reply to all of this, but I think there are some misconceptions in this thread. I don't think Objective-C interop is really supported on Linux.

Also, you can (and imo should) use C++ interop, which will let you call C++ functions and use C++ types in Swift without any bridging. To enable C++ interop, you can use the flags we discussed earlier. If you use C++ interop, there is no need for the extern C blocks anywhere.

Good luck.

1 Like