« On the market | Main | Language Barrier »

Making C++ and Objective C play nice

Ah, sibling rivalry. C++ is one year older than Objective C, and while they have the same parent, C, they're quite different. But, sometimes, you want to harness code written in C++ from your nice and comfy Cocoa-based app. Sure, you can try Objective C++, the hybrid as of late, but you risk losing Objective C's pure superset abilities, and the compiler takes a speed hit trying to be bilingual. And if other coders either can't or won't handle C++, well, then it's time to break out some pure C wrappers and contain things. Here's a few tips I've learned the hard way.

The first step, of course, is to make a header file to be our bridge keeper. Since Objective C reads C straight across and is toll-free with some Core Foundation structs, and C++ will let you use a plain C header if you uglify things, the common ground of choice is a C header and a C++ wrapper, using Core Foundation.

External circumstances

#if __cplusplus
extern "C" {
#endif
#include <CoreFoundation/CoreFoundation.h>
//Your function declarations here!
#if __cplusplus
} //Extern C
#endif
It's also good form to leave the CoreFoundation include in the .cpp file instead. Cocoa.h already includes CoreFoundation, so you don't need to worry about the objective-C end of things. Since we care only about using the C++ library, and not making a C++ citizen ourselves, it's okay to keep it all in the extern C world. This means no operator overloading of our functions among other things.

Furthermore, the overloading ban only applies to function declarations. That means the actual .cpp file can have that comfy extern wrapper all about, instead of prefixing each function with extern "C".

#if __cplusplus
extern "C" {
#endif
int foo() {return 1;}
int bar() {return 0;}
#if __cplusplus
} //Extern C
#endif
is the same as
extern "C" int foo() {return 1;}
extern "C" int bar() {return 0;}
While the latter has less lines of code in the example, the former will help you from tripping up and forgetting the wandering extern C calls in front of each function. If you're used to Objective C, you'll want to do this. Trust me on this one.

Cocoa to the Core

I suppose in theory you could simply use Objective C++ constrained to one file, but in unfamiliar territory, it's good to ensure strong language boundaries. Thus, using Core Foundation with Toll free bridging. Apple's already done a lot of the work explaining it, but I want to add this tidbit that slipped under my radar.

Core Foundation classes can do more than NSObjects. In fact, it's possible to use a CFArrayRef to store structs and other non-object items. But we want NSObjects. So don't forget to use the pre-made callbacks.

That is, you'll want CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); and CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);. If you use NULL instead in the callback arguments, keys and values will be neither retained nor released, causing either crashes or memory leaks.

Sausage Links

Last but not least is if you really want to contain the C++, not even letting it into your main project, but keeping it in a static .a library file. Things get strange. In test projects, it'll all link properly. In projects that make the new wrapper library, it'll all link properly. But in the final project, when I tried to build, I got dozens of linker errors, spanning kilobytes if not more:
Undefined symbols:
  "std::basic_string<char, std::char_traits<char>, std::allocator<char> >::end()", referenced from:
      Foo() in libMyFooLib.a (foo.o)
ld: symbol(s) not found

It's a huge huge mess of text, and at first I thought my new library wasn't finding the c++ library it wrapped. I tried all sorts of linker arguments and prelinking paths and hopping on one foot while doing a rain dance. Finally, I threw in a dummy voodoo.cpp file, just because it was worth a shot. Lo and behold, it compiled! And sure enough, the devil's in the build details. A plain cocoa app uses /Developer/usr/bin/gcc-4.0, whereas any that has a cpp in the mix uses /Developer/usr/bin/gcc++-4.0! Since having a voodoo.cpp is bad form, the workaround is rather simple: make sure /usr/lib/libstdc++.6.dylib is also in the link chain.

That's about it for now! I was going to add in a little trick involving redefining classes as structs, but it turns out it's too unreliable to suggest. So if you absolutely can't find common interlanguage data types, there's always void*. Just be careful, and keep it under wraps.

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

About

This page contains a single entry from the blog posted on April 11, 2008 10:26 PM.

The previous post in this blog was On the market.

The next post in this blog is Language Barrier.

Many more can be found on the main index page or by looking through the archives.

Creative Commons License
This weblog is licensed under a Creative Commons License.
Powered by
Movable Type 3.35