Skip to content

Use Objective-C Classes in C++ Opaquely, Readably and Safely

2011/03/27

Note: I added a footnote. Go read it.

Also note: I wrote an updated post on this topic. Basically just ignore the rest of this article and follow this link instead.

One of my many pet projects currently involves mixing C++ and Objective-C a lot (I am experimenting with porting a cross platform C++ windowing library to OS X). While this is generally not a problem, anybody who has tried to wrap some Mac/iPhone functionality that is exposed via the Cocoa/AppKit/UIKit/… APIs should be familiar with the following problem:

Let’s assume you built a command line speech synthesizer using NSSpeechSynthesizer, that just speaks every command line parameter given to it.

#import <AppKit/AppKit.h>

int main(int argc, char *argv[])
{
	NSSpeechSynthesizer * synth = [[NSSpeechSynthesizer alloc] init];
	for (int word = 1; word < argc; ++word)
	{
		[synth startSpeakingString:
			[NSString stringWithUTF8String:argv[word]]
		];
		while ([synth isSpeaking]);
	}
}

Now imagine you are casually contributing to a library a friend of yours is building and you both decide it would be cool to have this functionality built into this library. Unfortunately it is a C++ only library, so you have to wrap all the Objective-C specifics into a nice C++ class. It could look like this (lets call this file speaker.hpp from now on):

#ifndef YOUR_FRIENDS_LIBRARY_SPEECH_SPEAKER_HPP
#define YOUR_FRIENDS_LIBRARY_SPEECH_SPEAKER_HPP

#import <AppKit/AppKit.h>
#include <string>

namespace your
{
namespace friends
{
namespace library
{
namespace speech
{

class speaker
{
public:
	speaker();
	~speaker();

	void
	speak(
		std::string const &
	);

	bool
	is_speaking() const;

private:
	NSSpeechSynthesizer * const synth_;
};

}
}
}
}

#endif

with the following implementation in speaker.mm (.mm to let the compiler know that this is an “Objective-C++” file):

#include <speaker.hpp>

your::friends::library::speech::speaker::speaker():
	synth_(
		[[NSSpeechSynthesizer alloc] init]
	)
{
}

your::friends::library::speech::speaker::~speaker()
{
	[synth_ release];
}

void
your::friends::library::speech::speaker::speak(
	std::string const & string
)
{
	[synth_ startSpeakingString:
		[NSString stringWithUTF8String:
			string.c_str()
		]
	];
}

bool
your::friends::library::speech::speaker::is_speaking() const
{
	return [synth_ isSpeaking];
}

OK, so we have wrapped the AppKit calls in a C++ class, but what happens if someone wants to use this class? They are going to include our header speaker.hpp, of course. And with this they will import AppKit.h,an Objective-C header, which means that their compiler will have to understand this language.

What we actually want, though, is to make the usage of Objective-C in the background completely opaque to the user of our C++ library. The first attempt to do this, that one might come up with, is to use void * instead of NSSpeechSynthesizer in our header. This seems perfectly legal as the Objective-C method-call (actually message-sending) syntax is not limited to Objective-C interface pointers; you can send a message to a void * and if that pointer happens to point to an instance of an Objective-C interface offering the called method, everything will work fine (even if the pointer will point to NULL or nil, the App will not crash; nothing will happen though and if your pointer points to garbage data the behavior is undefined).

Lets just try it: We remove the import statement for AppKit from speaker.hpp and move it to the implementation file. Also in speaker.hpp we change line 30 (previously 31) to

	void * const synth_;

and compile. If you are using clang, you will see the problem of this approach:

/Users/julian/Documents/Blog/objcpp/speaker.mm: In destructor ‘your::friends::library::speech::speaker::~speaker()’:
/Users/julian/Documents/Blog/objcpp/speaker.mm:13: warning: invalid receiver type ‘void *’
/Users/julian/Documents/Blog/objcpp/speaker.mm: In member function ‘void your::friends::library::speech::speaker::speak(const std::string&)’:
/Users/julian/Documents/Blog/objcpp/speaker.mm:25: warning: invalid receiver type ‘void *’
/Users/julian/Documents/Blog/objcpp/speaker.mm: In member function ‘bool your::friends::library::speech::speaker::is_speaking() const’:
/Users/julian/Documents/Blog/objcpp/speaker.mm:31: warning: invalid receiver type ‘void *’

As said: The compiler will let us do this (it “just” generates warnings), but we lose type-safety. The compiler will no longer generate warnings or errors if we send invalid messages to our NSSpeechSynthesizer. We could even write

[synth_ crashTheApp];

in speaker.mm and it would compile (and would actually even crash the program … ain’t I funny :P). The second issue with this approach is lacking readability, of course. void * says nothing about what synth_ actually is.

So what is the right way to do this then? Well, one way is of course the (in-)famous Pimpl-Pattern. This I don’t want to cover here. I want to present a slightly simpler way of doing it; without the overhead of a private implementation pointer. If you are using Pimpl anyway, though, then you won’t need the following “hack”.

What we are going to do is to create a new type – lets call it your::friends::library::speech::synthesizer – which will be determined by whether we compile our library — where we can use Objective-C all we like — or whether we are compiling a C++ source file that just includes our header. This will of course boil down to some preprocessor magic. We are going to define our new type in a new header file synthesizer.hpp:

#ifndef YOUR_FRIENDS_LIBRARY_SPEECH_SYNTHESIZER_HPP
#define YOUR_FRIENDS_LIBRARY_SPEECH_SYNTHESIZER_HPP

#ifdef __OBJC__
#import <AppKit/AppKit.h>
#endif

namespace your
{
namespace friends
{
namespace library
{
namespace speech
{

#ifdef __OBJC__
typedef NSSpeechSynthesizer synthesizer;
#else
typedef void synthesizer;
#endif

}
}
}
}

#endif

The #ifdef __OBJC__ is a pattern you see for example in the “prefix header” that Xcode generates for every project; __OBJC__ is only defined when you compile an Objective-C source file (file extension .m and .mm for Objective-C++). When we are compiling an Objective-C source file anyway (as we do when building our library) we use the proper AppKit class to get type safety. When we want to compile a file as pure C++ (file extension .cpp, .cxx, .c++, ...) we use void. Defining a type as void may seem awkward, but we will always only be using pointers of our new type synthesizer (as there is no such thing as stack allocated instances of Objective-C classes). This also alleviates the fear of different layouts of our class speaker depending on our source file type: Pointers always have the same size, regardless of their type. However, if that bugs you, feel free to just include the little “*” in the two typedefs.1

Notice that a standard forward declaration of the form

class NSSpeechSynthesizer;

in speaker.hpp does not work. On my system it fails with the following error:

In file included from /System/Library/Frameworks/AppKit.framework/Headers/AppKit.h:68,
                 from /Users/julian/Documents/Blog/objcpp/speaker.mm:2:
/System/Library/Frameworks/AppKit.framework/Headers/NSSpeechSynthesizer.h:44: error: ‘NSSpeechSynthesizer’ redeclared as different kind of symbol
/Users/julian/Documents/Blog/objcpp/./speaker.hpp:3: error: previous declaration of ‘struct NSSpeechSynthesizer’

For further study I put the library we just developed up as a Gist, along with a small program using it (basically the first code snipped) without having to be compiled as Objective-C. I hope I’ll be forgiven for the lack of a proper directory structure; Gists don’t support folders.


1 Actually the C++ standard only guarantees that pointers to layout compatible types have the same value represenation and alignment requirements, so the two classes could actually end up having different representations in memory. I don’t yet know what implications this has, but if you want to be on the safe side, just use Pimpl.

About these ads

From → Mac Development

3 Comments
  1. Eugene permalink


    #ifdef __OBJC__
    @class NSSpeechSynthesizer;
    #endif
    struct NSSpeechSynthesizer;

    This will compile everywhere (C,C++,Obj-C,Obj-C++), and you can do whatever you want with this NSSpeechSynthesizer forward declaration.

    • Julian Kniephoff permalink

      Seems more elegant, indeed, but probably suffers from the same problems my approach does, doesn’t it?

Trackbacks & Pingbacks

  1. Revisiting Using Objective-C-Objects in C++ Classes in the Age of ARC « Filling in the Details

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: