Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
This gist is a simple example on how to call a function written in swift from C/C++, without taking the detour via Objective-C/C++.

Swift Meets C/C++

This gist is a simple example on how to call a function written in swift from C/C++, without taking the detour via Objective-C/C++.


Analytics

In this example we're going to invoke a function called say_hello, which, as the name already suggests, prints "Hello, World!" to the terminal.

say_hello is defined as following:

// func.swift

@_cdecl("say_hello")
public func say_hello(){
    print("Hello, World!")
}

This is just the canonical "Hello, World!"-example wrapped in a function called say\_hello. The only thing, that stands out, is the attribute @_cdecl("say\_hello").

@_cdecl is an undocumented attribute, which according to vague informations found on the web, enforces C-name mangling. Note: @_cdecl solely enforces the name mangling; not the calling convention!

In order to link our code with a C/C++ application, will need a library. In order to compile our code as a linked library, specify -emit-library when compiling the .swift-file.

We want to link our swift code with our C/C++ applications, which means, that we'll need this code to be compiled as a library. In order to compiler our code as a linked library, just specify '-emit-library' when compiling the .swift-file.

swiftc func.swift -emit-library

This will result in a linked library called "libfunc.dylib" (or "libfunc.so" on Linux, I suppose), which exports our function say_hello as _say_hello.


Now to our C++:

// main.cpp

// only "extern" when targeting C.
extern "C" void say_hello();

int main(){
    say_hello();    // Prints "Hello, World!"
    return 0;
}

Here we declare say_hello as an external reference, which will be provided by the library, we compiled before.

What's left at this point is just to compile and link our code.

gcc -std=c++11 -c main.cpp -o main.o 
gcc libfunc.dylib main.o -o main

When we run the application, we're getting greeted with following:

./main
> Hello, World!

This was my first "technical article". Feedback is welcome!

None the less, I hope I could help you with this "article".

J. Kirsch

Resources

  • SO Answer by Max Desiatov
  • Trial'n'Error with the command line tool nm
  • Other links, that I cannot remember... :-(
@HiImJulien
Copy link
Author

HiImJulien commented Mar 24, 2019

I tinkered around with it recently and found, that the @_cdecl directive indeed also forces the cdecl-calling convention, which trivially enables us to pass arguments. See following example:

main.cpp:

#include <cstdint>

extern "C" void print_num(std::int32_t);

int main() {
    print_num(2);
    return 0;
}

lib.swift:

@_cdecl("print_num")
public func print_num(num: Int32) {
    print("The number is \(num)")
}

build.ninja:

builddir = bin
cc = gcc
cflags = -std=c++17
ldflags = -lstdc++

sc = swiftc
sflags = -emit-library

rule compile_cpp
    command = $cc $cflags -c $in -o $out

rule compile_swift
    command = $sc $sflags $in -o $out

rule link
    command = $cc $ldflags $in -o $out

build $builddir/main.o: compile_cpp main.cpp
build $builddir/lib:    compile_swift lib.swift
build $builddir/link:   link $builddir/main.o $
                             $builddir/lib $

default $builddir/link

For those who do not use the ninja build system (and have no idea what is going on above), you can compile this example using following terminal commands

gcc -std=c++17 -c main.cpp -o bin/main.o
swiftc -emit-library lib.swift -o bin/lib
gcc -lstdc++ -c bin/main.o bin/lib -o link

P.S.: I hacked this example out of my memory; there could be some trivial mistakes.

@marc-medley
Copy link

marc-medley commented Jul 29, 2019

In the second example, i found the last command to fail with the following warning:

gcc -lstdc++ -c bin/main.o bin/libframework.dylib -o link

# clang: warning: -Z-reserved-lib-stdc++: 
#    'linker' input unused [-Wunused-command-line-argument]
# clang: warning: bin/main.o: 
#    'linker' input unused [-Wunused-command-line-argument]
# clang: warning: bin/lib: 
#    'linker' input unused [-Wunused-command-line-argument]

However, the following did work:

swiftc -emit-library lib.swift -o bin/lib
gcc -std=c++17 -c main.cpp -o bin/main.o
gcc bin/main.o bin/lib -lstdc++ -o bin/linked

./bin/linked
# The number is 2

Alternately, a g++ approach also works.

swiftc -emit-library lib.swift -o bin/lib
g++ -std=c++17 -c main.cpp -o bin/main.o
g++ bin/main.o bin/lib -o bin/linked

./bin/linked
# The number is 2

As FYI, StackOverflow has some discussion of "What is the difference between g++ and gcc?", and "Which one should be used for general c++ development?".

@Raj123456788
Copy link

Raj123456788 commented Sep 19, 2020

Can you please tell me where to add this in swift project: "swiftc func.swift -emit-library" I am trying to call a swift from C++ but it keeps throwing me error: Unable to find print_num

@narek-king
Copy link

narek-king commented Nov 5, 2021

Can you please tell me where to add this in swift project: "swiftc func.swift -emit-library" I am trying to call a swift from C++ but it keeps throwing me error: Unable to find print_num

@Raj123456788 If I get the question correctly ,swiftc func.swift -emit-library is the command for building the swift code into binaries. It is not the part of the swift code but command for running in the terminal.

@narek-king
Copy link

narek-king commented Nov 5, 2021

Thank you for posting this. I wish I found this a weak ago.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment