Thursday, December 10, 2015

Swift using a C framework

Recently I explored how to write a framework in Swift and then import and use it in my Swift app. The key elements are to find a good place to stash the framework (I used ~/Library/Frameworks), and to properly let Xcode know that it should link that framework into the app.

The objective for this post is to learn how to import and use a framework not written in Swift. Before I use a real one (like this) for today I am going to use a very simple example written in C, following this post from a few years ago.

Here is the code, which provides amazing functionality:

There is a slight wrinkle. In the original version, we didn't use a header file but instead declared the functions as extern like this:

extern int f1(int x);

within the file that uses them.

As an aside, note that I should probably have used #import rather than #include. See for example, here. However, I had already gone through twice re-testing the steps for today's post, and I didn't want to do it all again.

Now we do:

> clang -g -Wall -c add*.c
>

which generates add1.o and add2.o. Then

> clang -g -Wall useadd.c add1.o add2.o -o useadd
>


> ./useadd
f1: 1; main 2
f2: 10; main 12
>

Although we didn't explicitly tell clang about add.h (i.e. in the command line invocation), it is needed for this to work. I suppose clang finds the header file in the build directory.

Now, let's make an old-fashioned library. Delete useadd. Do:

> libtool -static add*.o -o libadd.a
>

We have libadd.a. Let's use it:

> clang -g -Wall -o useadd useadd.c -L. -ladd
>

And it works:

> ./useadd
f1: 1; main 2
f2: 10; main 12
>

Here we have explicitly directed clang with -L. to the build directory, where we want it to look for -ladd (short for libadd).

Now the goal is to make a Cocoa app that uses f1 and f2... whether written in Objective C or in Swift. I thought at first we would need a framework, but we don't. Just copy libadd.a into ~/Library/Frameworks. Make a new Xcode project Myapp, a Cocoa app in Objective C. Add the library to the project as described (by clicking + on Linked Frameworks and Libraries, etc.)

We still have the header issue. For this version using libadd.a I just dragged the header into the project, with copy files, and then did the import in either AppDelegate.h or AppDelegate.m.

Now, for a framework. Make a new Xcode framework in Objective C, called Adder. Drag in add1.c and add2.c and do copy files. Put the declarations from add.h into Adder.h which Xcode provided for us.

Build it. Use the Show in Finder trick to find and then drag the framework to the Desktop and then to ~/Library/Frameworks.

Try to use the framework from the command line.

I specified the path to find the header folder which is in the framework.

And it works:


> clang -g -o useadd -F ~/Library/Frameworks/ -framework Adder useadd.c\
-I~/Library/Frameworks/Adder.framework/Headers

> ./useadd
f1: 1; main 2
f2: 10; main 12
>

We move away from the command line. Make a new Xcode Project for a Cocoa app in Objective C. Call it MyApp.

In the AppDelegate do #import <Adder/Adder.h>. Fix the error that this introduces by adding the linked binary, as usual. In applicationDidFinishLaunching: do:

int n = f1(10);
NSLog(@"%d", n);

Build and run. It works. The debugger prints:

f1: 10;2015-12-10 08:33:28.318 MyApp[6168:235988] 11

Now for the last step. Make a new Xcode Project for a Cocoa app in Swift. Call it MySwiftApp. In the AppDelegate do import Adder. Fix the error by adding the framework. In applicationDidFinishLaunching add:

let result = f1(10)
Swift.print("\nresult: \(result)")

And it works! The debugger prints:

f1: 10;
result: 11

The print from C didn't include a newline. Oops.

It works without a "bridging header". If you do have to generate one of those, just add a dummy Objective C module to your project. Xcode will generate the bridging header for you.

At this point, I have the confidence to move ahead with a real project.

[UPDATE: Repeating it all again for the third or fourth time, I am unable to get the last two steps to work smoothly. The Objective C one builds with a warning "implicit declaration of function 'f1' is invalid in C99", but it runs and prints what we expect. The Swift example allows me to "import Adder" but doesn't recognize the symbol "f1".

Since it really did work before (I didn't just imagine it), there is something that I "got for free" before that I am missing now. More exploration is needed.]

[UPDATE 2: I went through every step again from scratch. It works now. I wrote it up for the book. I guess I just have to wait and see if it fails again. ]