views:

325

answers:

2

I have a small C++ application which I imported Objective-C classes. It works as Objective-C++ files, .mm, but any C++ file that includes a header which may end up including some Objective-C header must be renamed to a .mm extension for the proper GCC drivers.

Is there a way to write either a purely C++ wrapper for Objective-C classes or can I separate the Objective-C objects out somehow and just link them separately? Maybe even if the Objective-C classes became a small library I could statically re-link at compile time?

The problem is that this code is cross-platform, and it is more difficult to compile on systems that normally do not use Objective-C (i.e. not Macs). Even though preprocessor commands restrict any implementation of Objective-C code on Windows or Linux, the original code still has .mm extensions and GCC still treats the code as Objective-C++.

+3  A: 

Usually you simply wrap your Objective-C classes with C++ classes by e.g. using opaque pointers and forwarding calls to C++ methods to Objective-C methods.

That way your portable C++ sources never have to see any Objective-C includes and ideally you only have to swap out the implementation files for the wrappers on different platforms.

Example:

// c++ header:
class Wrapper {
    struct Opaque;
    Opaque* opaque;
    // ...
public:
    void f();
};

// Objective-C++ source on Mac:
struct Wrapper::Opaque {
    id contained;
    // ...
};

void Wrapper::f() {
    [opaque->contained f];
}

// ...
Georg Fritzsche
so then would struct Wrapper::Opaque include 'OBJCClass *name = [[OBJCClass alloc] init]' and Wrapper::f() would call [name f] if it was so linked?
mike_b
Georg Fritzsche
+2  A: 

Yes, that's easily possible, both ways, if you know a few tricks:

1) The "id" type is actually defined in a plain C header. So you can do the following:

In your header:

#include <objc/objc.h>

class MyWindow
{
public:
    MyWindow();
    ~MyWindow();
protected:
    id        mCocoaWindow;
};

In your implementation (.mm):

#include "MyWindow.h"
#include <Cocoa/Cocoa.h>

MyWindow::MyWindow()
{
    mCocoaWindow = [[NSWindow alloc] init];
}

MyWindow::~MyWindow()
{
    [mCocoaWindow release];
    mCocoaWindow = nil;
}

2) There are two preprocessor constants you can use to exclude C++/ObjC-specific code when a source file includes them that is one of the two, but not ObjC++:

#if __OBJC__
// ObjC code goes here.
#endif /* __OBJC__*/

#if __cplusplus
// C++ code goes here.
#endif

Just be careful, you can't just add/remove ivars or virtual methods with an #ifdef, that will create two classes with different memory layouts and make your app crash in very weird ways.

3) You can use a pointer to a struct without declaring its contents:

In your header:

@interface MyCppObjectWrapper : NSObject
{
    struct MyCppObjectWrapperIVars    *ivars;    // This is straight ObjC, no ++.
}

@end

In your implementation file (.mm):

struct MyCppObjectWrapperIVars
{
    std::string    myCppString1;
    std::string    myCppString2;
    std::string    myCppString3;
};

@implementation MyCppObjectWrapper

-(id)  init
{
    if(( self = [super init] ))
    {
        ivars = new MyCppObjectWrapperIVars;
    }

    return self;
}

-(void) dealloc
{
    delete ivars;
    ivars = NULL;

    [super dealloc];
}

@end

This will make your header simple standard C or ObjC, while your implementation file gets constructors/destructors of all ivars called without you having to create/delete each one as an object on the heap.

This is all only the Mac side of things, but this would mean that you could keep the ObjC stuff out of your headers, or at least make it compile when used from cross-platform client files of the Mac implementation of your C++ portability layer.

uliwitness