Writing a C++ Wrapper around Objective-C
Asked Answered
G

4

12

I want to call and work with Objective-C classes from within a C++ project on OS X. It is time to start moving towards all Objective-C, but we need to do this over some time.

How does one go about accomplishing this? Can anyone shed some light and provide an example?

Germinant answered 31/3, 2011 at 18:30 Comment(4)
Are you trying to call Foundation (non-UI) objects or AppKit (UI) objects? Are you calling NSApplicationMain() in main() or are you trying to avoid that?Reefer
possible duplicate of Calling Objective-C method from C++ method?Baccivorous
There is an objective-c++ tag on SO that you should check out as well. stackoverflow.com/questions/tagged/objective-c++Baccivorous
@Rob Napier - Both Foundation and App Kit. Filesystem calls and dialog boxes for opening files, saving, alerts, etc.Germinant
T
9

Objective-C++ is a superset of C++, just as Objective-C is a superset of C. It is supported by both the gcc and clang compilers on OS X and allows you to instantiate and call Objective-C objects & methods from within C++. As long as you hide the Objective-C header imports and types within the implementation of a C++ module, it won't infect any of your "pure" C++ code.

.mm is the default extension for Objective-C++. Xcode will automatically do the right thing.

So, for example, the following C++ class returns the seconds since Jan 1., 1970:

//MyClass.h

class MyClass
{
  public:
    double secondsSince1970();
};

//MyClass.mm

#include "MyClass.h"
#import <Foundation/Foundation.h>

double MyClass::secondsSince1970()
{
  return [[NSDate date] timeIntervalSince1970];
}

//Client.cpp

...
MyClass c;
double seconds = c.secondsSince1970();

You will quickly find that Objective-C++ is even slower to compile than C++, but as you can see above, it's relatively easy to isolate its usage to a small number of bridge classes.

Tindall answered 31/3, 2011 at 18:46 Comment(0)
C
9

I think it was Phil Jordan that really put forward the best C++ wrapped Obj-C formula. However, I think the latter, C++ wrapped by Obj-C is more useful. I'll explain why below.

Wrapping an Objective-C object with C++

Person.h - The Obj-C header

@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end

PersonImpl.h - The C++ header

namespace people {
    struct PersonImpl;

    class Person
    {
    public:
        Person();
        virtual ~Person();

        std::string name();
        void setName(std::string name);

    private:
        PersonImpl *impl;
    };
}

Person.mm - The Obj-C++ implementation

#import "Person.h"

#import "PersonImpl.h"

namespace people {
   struct PersonImpl
   {
      Person *wrapped;
   };

   Person::Person() :
   impl(new PersonImpl())
   {
      impl->wrapped = [[Person alloc] init];
   }

   Person::~Person()
   {
      if (impl) {
         [impl->wrapped release]; // You should turn off ARC for this file. 
                                  // -fno-objc-arc in Build Phases->Compile->File options
      }

      delete impl;
   }

   std::string Person::name()
   {
      return std::string([impl->wrapped UTF8String]); 
   }

   void Person::setName(std::string name)
   {
      [impl->wrapped setName:[NSString stringWithUTF8String:name.c_str()]];
   }
}

@implementation Person
@end

Wrapping a C++ object with Objective-C

I often find the real problem isn't getting C++ to talk to Obj-C code, it's switching back and forth between the two where things get ugly. Imagine an object that needs to have C++-only items, but the basic object details are filled out in Obj-C land. In this case, I write the object in C++ and then make it so I can talk to it in Obj-C.

Person.h - The C++ header

namespace people
{
   struct PersonImpl;

   class Person
   {
   public:
      Person();
      Person(Person &otherPerson);
      ~Person();
      std:string name;

   private:
      PersonImpl *impl;
   }
}

Person.cpp - The C++ implementation

namespace people
{
   struct PersonImpl
   {
       // I'll assume something interesting will happen here.
   };

   Person::Person() :
   impl(new PersonImpl())
   {
   }

   Person::Person(Person &otherPerson) :
   impl(new PersonImpl()),
   name(otherPerson.name)
   {
   }

   ~Person()
   {
      delete impl;
   }
}

Person.h - The Obj-C header

@interface Person : NSObject
@property (unsafe_unretained, nonatomic, readonly) void *impl;
@property (copy, nonatomic) NSString *name;
@end

Person.mm - The Obj-C++ implementation

@interface Person ()
@property (unsafe_unretained, nonatomic) std::shared_ptr<people::Person> impl;
@end

@implementation Person

- (instancetype)init
{
   self = [super init];
   if (self) {
      self.impl = std::shared_ptr<people::Person>(new people::Person());
   }

   return self;
}

- (instancetype)initWithPerson:(void *)person
{
   self = [super init];
   if (self) {
      people::Person *otherPerson = static_cast<people::Person *>(person);
      self.impl = std::shared_ptr<people::Person>(new people::Person(*otherPerson));
   }

   return self;
}

- (void)dealloc
{
   // If you prefer manual memory management
   // delete impl;
}

- (void *)impl
{
   return static_cast<void *>(self.impl.get());
}

- (NSString *)name
{
   return [NSString stringWithUTF8String:self.impl->name.c_str()];
}

- (void)setName:(NSString *)name
{
   self.impl->name = std::string([name UTF8String]);
}

@end

Regarding the void *

The second you stepped into the land of C++ you were going to feel some pain if you want to avoid your whole project being littered with .mm files. So, let's just say, if you don't think it is ever necessary to get your C++ object back out, or reconstitute the Obj-C object with the C++ object you can remove that code. It is important to note that the second you remove the Person instance from the Obj-C code through the void * method you had better make your own copy with the copy constructor or the pointer will become invalid.

Cromorne answered 22/2, 2015 at 16:35 Comment(0)
H
3

First rename your files from *.m to *.mm so you get Objective-C++

I have not tired this, so it is speculation (I will tonight):

As all Objective-C++ objects (that are reference counted) are controlled via pointers so you can write a special destructor for shared pointer.

template<typename T>
struct ObjCDestruct
{
    void operator()(T* obj)
    {
        [obj release];
    }
};

Now you can stick your Objective-C obects in a boost::shared_ptr

// FuncFile.M
//
int func()
{
    boost::shared_ptr<MyX, ObjCDestruct<MyX> >  data([[MyX alloc] init]);

    [data.get() doAction1:@"HI"];
}
Hutson answered 31/3, 2011 at 18:51 Comment(1)
if you wanted to be extra-snazzy, you'd subclass type_traits for is_ObjC_obj, and specialize on that ;)Laband
C
0

Check out this question Calling Objective-C method from C++ method?

You will need to have some objective-c classes to wrap the code and expose with a C function.

Consign answered 31/3, 2011 at 18:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.