25

So, I was bored today, and decide to mess with C++/Obj-C interpolation, and I found a way to create a very interesting setup.

@protocol NSCPPObj <NSObject>

-(id) init;
-(id) initWithInt:(int) value;
-(int) somethingThatReturnsAValue;
-(void) doSomething;

@end

class NSCPPObj : objc_object {
public:    
    static Class cls();

    int iVar;

    NSCPPObj();
    NSCPPObj(int);

    int somethingThatReturnsAValue();
    void doSomething();
};

As you can see, the interface is quite straightforward, and easy to understand. We create two (almost) identical interfaces, one for a C++ object, and another for a Obj-C protocol.

Now, I found a way to implement this, but brace yourself, this gets ugly:

// NSCPPObj.mm
#import <objc/runtime.h>
#import <iostream>

#import "NSCPPObject.h"

Class NSCPPObj_class = nil;

__attribute__((constructor))
static void initialize()
{
    NSCPPObj_class = objc_allocateClassPair([NSObject class], "NSCPPObj", 0);

    class_addMethod(NSCPPObj_class->isa, @selector(alloc), imp_implementationWithBlock(^(id self) {
        return class_createInstance(NSCPPObj_class, sizeof(struct NSCPPObj));
    }), "@@:");

    class_addMethod(NSCPPObj_class, @selector(init), imp_implementationWithBlock(^(id self) {
        return self;        
    }), "@@:");

    class_addMethod(NSCPPObj_class, @selector(initWithInt:), imp_implementationWithBlock(^(id self, int value) {
        ((struct NSCPPObj *) self)->iVar = value;

        return self;
    }), "@@:i");

    class_addMethod(NSCPPObj_class, @selector(doSomething), imp_implementationWithBlock(^(id self) {
        ((struct NSCPPObj *) self)->doSomething();
    }), "v@:");
    class_addMethod(NSCPPObj_class, @selector(somethingThatReturnsAValue), imp_implementationWithBlock(^(id self) {
        return ((struct NSCPPObj *) self)->somethingThatReturnsAValue();
    }), "i@:");

    objc_registerClassPair(NSCPPObj_class);
}

Class NSCPPObj::cls()
{
    return NSCPPObj_class;
}

NSCPPObj::NSCPPObj()
{
    this->isa = NSCPPObj_class;
    [((id<NSCPPObj>) this) init];
}

NSCPPObj::NSCPPObj(int value)
{
    this->isa = NSCPPObj_class;
    [((id<NSCPPObj>) this) initWithInt:value];
}

void NSCPPObj::doSomething()
{
    std::cout << "Value Is: " << [((id<NSCPPObj>) this) somethingThatReturnsAValue] << std::endl;
}

int NSCPPObj::somethingThatReturnsAValue()
{
    return iVar;
}

I'll summarize what this does:

  1. Allocates a Class Pair
  2. Adds all class and instance methods to the object
  3. Registers the class Pair

Now, as you can see, this isn't very flexible, but it does work, and it's a two-way street:

id<NSCPPObj> obj = [[NSCPPObj::cls() alloc] initWithInt:15];
[obj doSomething];

NSLog(@"%i", [obj somethingThatReturnsAValue]);
NSLog(@"%@", obj);

NSCPPObj *objAsCPP = (__bridge NSCPPObj *) obj;

objAsCPP->doSomething();
std::cout << objAsCPP->somethingThatReturnsAValue() << std::endl;

You can also create the object by using new NSCPPObj(15), but remember to delete it! Obviously, this can work in a ARC or non-ARC environment, but ARC requires a few extra bridged casts.

So, I come to the real question:
What are the pros/cons of this design structure? I can list a few off of the top of my head:

Pros:

  1. Operator Overloading with C++
  2. Dynamic method binding with ObjC
  3. Can be constructed in either a C++ or ObjC fashion

Cons:

  1. Hard-to-read implementation
  2. Selectors & bindings must be added for every C++ implementation added to the interface
  3. Class object cannot be referenced directly

So, after all that, would you recommend this design structure in an application? and why.

bbum
  • 162,346
  • 23
  • 271
  • 359
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
  • 1
    I know too little C++ to give this a good go answering, but I wonder if the answer to this depends on exactly what kind of application you're working on. An existing C++ game being ported over could find this much more useful than a simple utility app...and a seasoned C++ programer may appreciate it more than someone who's heavily Objective-C oriented. – lxt Apr 04 '12 at 15:53
  • 1
    Voting to reopen. I understand how this may be 'not constructive', but this is a community site. I recognize you are a moderator, but seeing as you are the only one who wanted this to close. – Richard J. Ross III Apr 04 '12 at 17:04
  • I'm not a moderator and I flagged this question for review. It is off-topic. – N_A Apr 04 '12 at 17:24
  • 6
    This is a constructive, on-topic, question that is helpful to Mac OS X and iOS developers. This is an issue often faced by developers in that market that are leveraging any of the several hundred (thousands?) of various C++ engines that are available. Certainly, the example, itself is superfluous to the pros/cons/specific-question at the end, but the actual question of *Is generic bridging between ObjC and C++ a recommended pattern and why?* is very much a concrete question of value! – bbum Apr 04 '12 at 17:49
  • 1
    I'd also like to note that the same operator-overloading that you get here could be duplicated with a C++ wrapper around an Objective-C object. Then, provide an implicit conversion to `id`. I'm not endorsing that technique, but pointing out that it is probably a simpler solution to that particular problem. – Jonathan Sterling Apr 04 '12 at 18:21

1 Answers1

23

So, after all that, would you recommend this design structure in an application? and why.

No.

It is a really nice bit of code; I particularly like the use of imp_implementationWithBlock() (but I admit I might be partial to that particular feature of the runtime ;). And, of course, explorations like this are always an incredibly valuable learning tool.

The issue, in the context of "real world paying project" use, is that you are effectively creating a relatively generic bridge that will then have to have specific bridges at either end to interface with either typical C++ libraries or typical Objective-C APIs/libraries. To put it another way, you have effectively created a new runtime derived from an amalgamation of two existing runtimes.

And, as you point out in the Cons, you pretty much have to touch, wrap, modify and/or debug a shim on top of every C++ class you want to bring into this pattern.

In working with quite a bit of Objective-C++ code over the last 20+ years, a bridge like this is generally more trouble than it is worth. You would likely be better off -- spend less time writing and debugging code -- creating simple Objective-C wrappers around the C++ (or C, frankly) APIs that can then be integrated with and consumed by the targeted system's Objective-C frameworks.

bbum
  • 162,346
  • 23
  • 271
  • 359