【问题标题】:Calling Objective-C method from C++ member function?从 C++ 成员函数调用 Objective-C 方法?
【发布时间】:2010-11-06 20:32:13
【问题描述】:

我有一个类 (EAGLView),它可以毫无问题地调用 C++ 类的成员函数。现在,问题是我需要调用 C++objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];C++ 语法中我不能这样做。

我可以将这个 Objective-C 调用包装到同一个 Objective-C 类,该类首先调用 C++ 类,但是我需要以某种方式从 C++ 调用该方法,我不知道该怎么做它。

我试图将指向 EAGLView 对象的指针指向 C++ 成员函数,并在我的 C++ 类头中包含“EAGLView.h”,但出现 3999 个错误..

那么.. 我应该怎么做呢?举个例子就好了。我只找到了纯 C 这样做的例子。

【问题讨论】:

    标签: c++ objective-c


    【解决方案1】:

    您可以将 C++ 与 Objectiv-C (Objective C++) 混合使用。在您的 Objective C++ 类中编写一个 C++ 方法,该方法只需调用 [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; 并从您的 C++ 中调用它。

    我之前没有尝试过,但试一试,并与我们分享结果。

    【讨论】:

    • 但问题是我怎么称呼它......因为如果我在我的 C++ 类中包含“EAGLview.h”,我会遇到成千上万的错误。
    【解决方案2】:

    您可以将代码编译为 Objective-C++ - 最简单的方法是将 .cpp 重命名为 .mm。如果包含EAGLView.h,它将正确编译(因为 C++ 编译器不理解任何特定于 Objective-C 的关键字,您会遇到很多错误),并且您可以(在大多数情况下)混合使用 Objective-C 和C++ 随你喜欢。

    【讨论】:

    • 您是否在 this C++ 文件中收到所有这些编译器错误,或者它们是否在恰好包含此 C++ 标头的其他 C++ 文件中?
    • 看来我不能在 C++ 头文件中包含 EAGLView.h,因为它出于某种原因期望 Objective C 代码是 C++,并且不理解 @ + 其他符号
    【解决方案3】:

    您需要将您的 C++ 文件视为 Objective-C++。您可以通过将 foo.cpp 重命名为 foo.mm(.mm 是 obj-c++ 扩展名)在 xcode 中执行此操作。然后正如其他人所说,标准 obj-c 消息传递语法将起作用。

    【讨论】:

      【解决方案4】:

      如果您小心操作,您可以将 C++ 与 Objective-C 混合使用。有一些警告,但一般来说,它们可以混合使用。如果你想将它们分开,你可以设置一个标准的 C 包装函数,它为 Objective-C 对象提供一个来自非 Objective-C 代码的可用 C 样式接口(为你的文件选择更好的名称,我已经选择了这些名称冗长):

      MyObject-C-Interface.h

      #ifndef __MYOBJECT_C_INTERFACE_H__
      #define __MYOBJECT_C_INTERFACE_H__
      
      // This is the C "trampoline" function that will be used
      // to invoke a specific Objective-C method FROM C++
      int MyObjectDoSomethingWith (void *myObjectInstance, void *parameter);
      #endif
      

      MyObject.h

      #import "MyObject-C-Interface.h"
      
      // An Objective-C class that needs to be accessed from C++
      @interface MyObject : NSObject
      {
          int someVar;
      }
      
      // The Objective-C member function you want to call from C++
      - (int) doSomethingWith:(void *) aParameter;
      @end
      

      MyObject.mm

      #import "MyObject.h"
      
      @implementation MyObject
      
      // C "trampoline" function to invoke Objective-C method
      int MyObjectDoSomethingWith (void *self, void *aParameter)
      {
          // Call the Objective-C method using Objective-C syntax
          return [(id) self doSomethingWith:aParameter];
      }
      
      - (int) doSomethingWith:(void *) aParameter
      {
          // The Objective-C function you wanted to call from C++.
          // do work here..
          return 21 ; // half of 42
      }
      @end
      

      MyCPPClass.cpp

      #include "MyCPPClass.h"
      #include "MyObject-C-Interface.h"
      
      int MyCPPClass::someMethod (void *objectiveCObject, void *aParameter)
      {
          // To invoke an Objective-C method from C++, use
          // the C trampoline function
          return MyObjectDoSomethingWith (objectiveCObject, aParameter);
      }
      

      包装函数不需要与Objective-C类在同一个.m文件中,但它确实存在于中的文件需要编译为 Objective-C 代码。 CPP 和 Objective-C 代码中都需要包含声明包装函数的头文件。

      (注意:如果 Objective-C 实现文件的扩展名为“.m”,它将不会在 Xcode 下链接。“.mm”扩展名告诉 Xcode 期待 Objective-C 和 C++ 的组合,即 Objective -C++。)


      您可以使用PIMPL idiom 以面向对象的方式实现上述功能。实现只是略有不同。简而言之,您将包装函数(在“MyObject-C-Interface.h”中声明)放置在一个类中,并带有指向 MyClass 实例的(私有)void 指针。

      MyObject-C-Interface.h (PIMPL)

      #ifndef __MYOBJECT_C_INTERFACE_H__
      #define __MYOBJECT_C_INTERFACE_H__
      
      class MyClassImpl
      {
      public:
          MyClassImpl ( void );
          ~MyClassImpl( void );
      
          void init( void );
          int  doSomethingWith( void * aParameter );
          void logMyMessage( char * aCStr );
      
      private:
          void * self;
      };
      
      #endif
      

      请注意,包装方法不再需要指向 MyClass 实例的 void 指针;它现在是 MyClassImpl 的私有成员。 init 方法用于实例化一个 MyClass 实例;

      MyObject.h (PIMPL)

      #import "MyObject-C-Interface.h"
      
      @interface MyObject : NSObject
      {
          int someVar;
      }
      
      - (int)  doSomethingWith:(void *) aParameter;
      - (void) logMyMessage:(char *) aCStr;
      
      @end
      

      MyObject.mm (PIMPL)

      #import "MyObject.h"
      
      @implementation MyObject
      
      MyClassImpl::MyClassImpl( void )
          : self( NULL )
      {   }
      
      MyClassImpl::~MyClassImpl( void )
      {
          [(id)self dealloc];
      }
      
      void MyClassImpl::init( void )
      {    
          self = [[MyObject alloc] init];
      }
      
      int MyClassImpl::doSomethingWith( void *aParameter )
      {
          return [(id)self doSomethingWith:aParameter];
      }
      
      void MyClassImpl::logMyMessage( char *aCStr )
      {
          [(id)self doLogMessage:aCStr];
      }
      
      - (int) doSomethingWith:(void *) aParameter
      {
          int result;
      
          // ... some code to calculate the result
      
          return result;
      }
      
      - (void) logMyMessage:(char *) aCStr
      {
          NSLog( aCStr );
      }
      
      @end
      

      请注意,MyClass 是通过调用 MyClassImpl::init 来实例化的。您可以在 MyClassImpl 的构造函数中实例化 MyClass,但这通常不是一个好主意。 MyClass 实例是从 MyClassImpl 的析构函数中析构的。与 C 风格的实现一样,包装器方法只是遵循 MyClass 的相应方法。

      MyCPPClass.h (PIMPL)

      #ifndef __MYCPP_CLASS_H__
      #define __MYCPP_CLASS_H__
      
      class MyClassImpl;
      
      class MyCPPClass
      {
          enum { cANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING = 42 };
      public:
          MyCPPClass ( void );
          ~MyCPPClass( void );
      
          void init( void );
          void doSomethingWithMyClass( void );
      
      private:
          MyClassImpl * _impl;
          int           _myValue;
      };
      
      #endif
      

      MyCPPClass.cpp (PIMPL)

      #include "MyCPPClass.h"
      #include "MyObject-C-Interface.h"
      
      MyCPPClass::MyCPPClass( void )
          : _impl ( NULL )
      {   }
      
      void MyCPPClass::init( void )
      {
          _impl = new MyClassImpl();
      }
      
      MyCPPClass::~MyCPPClass( void )
      {
          if ( _impl ) { delete _impl; _impl = NULL; }
      }
      
      void MyCPPClass::doSomethingWithMyClass( void )
      {
          int result = _impl->doSomethingWith( _myValue );
          if ( result == cANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING )
          {
              _impl->logMyMessage( "Hello, Arthur!" );
          }
          else
          {
              _impl->logMyMessage( "Don't worry." );
          }
      }
      

      您现在可以通过 MyClassImpl 的私有实现访问对 MyClass 的调用。如果您正在开发可移植的应用程序,这种方法可能会很有利;您可以简单地将 MyClass 的实现换成另一个平台特定的实现......但老实说,这是否是一个更好的实现更多的是一个品味和需求的问题。

      【讨论】:

      • 嗨,我试过了,但我收到链接错误,提示找不到符号。即它找不到 MyObjectDoSomethingWith。有什么想法吗?
      • 您可能需要在int MyObjectDoSomethingWith 之前添加extern "C"
      • 已经试过了,不起作用,这是有道理的,因为当我们想从 C 调用 C++ 函数时使用 extern "C",在这种情况下,我们是从 C++ 调用 C 函数,不?
      • 另外,objectiveCObject 是如何在 MyCPPClass.cpp 中实例化的?
      • 真棒@dreamlax,现在正在编译,但我不知道称之为“someMethod”。应该添加哪些参数 :int MyCPPClass::someMethod (void *objectiveCObject, void *aParameter)???
      【解决方案5】:

      最简单的解决方案是简单地告诉 Xcode 将所有内容编译为 Objective C++。

      将您的项目或目标设置设置为 Compile Sources As to Objective C++ 并重新编译。

      那么你就可以到处使用C++或者Objective C了,例如:

      void CPPObject::Function( ObjectiveCObject* context, NSView* view )
      {
         [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)view.layer]
      }
      

      这与将所有源文件从 .cpp 或 .m 重命名为 .mm 具有相同的效果。

      这有两个小缺点:clang 无法分析 C++ 源代码;一些比较怪异的C代码在C++下编译不出来。

      【讨论】:

      • 我有点好奇,当您将所有内容编译为 Objective-C++ 时,您是否会收到有关使用 C 样式强制转换的警告和/或其他有关有效 C 样式代码的 C++ 特定警告?
      • 当然,您正在使用 C++ 进行编程,因此您应该表现得恰到好处 - 但作为一般规则,C++ 比 C 更好,即使您从未创建过类。它不会让你做愚蠢的事情,它会让你做一些好事(比如更好的常量和枚举等)。你仍然可以投射相同的内容(例如 (CFFloat)x)。
      【解决方案6】:

      有时将 .cpp 重命名为 .mm 并不是一个好主意,尤其是当项目是跨平台的时。在这种情况下,对于 xcode 项目,我通过 TextEdit 打开 xcode 项目文件,找到包含感兴趣文件的字符串,应该是这样的:

      /* OnlineManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OnlineManager.cpp; sourceTree = "<group>"; };
      

      然后将文件类型从 sourcecode.cpp.cpp 更改为 sourcecode.cpp.objcpp

      /* OnlineManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = **sourcecode.cpp.objcpp**; path = OnlineManager.cpp; sourceTree = "<group>"; };
      

      相当于将.cpp重命名为.mm

      【讨论】:

        【解决方案7】:

        另外,您可以调用 Objective-C 运行时来调用该方法。

        【讨论】:

          【解决方案8】:

          第 1 步

          创建一个目标c文件(.m文件)及其对应的头文件。

          // 头文件(我们称之为“ObjCFunc.h”)

          #ifndef test2_ObjCFunc_h
          #define test2_ObjCFunc_h
          @interface myClass :NSObject
          -(void)hello:(int)num1;
          @end
          #endif
          

          //对应的Objective C文件(我们称之为“ObjCFunc.m”)

          #import <Foundation/Foundation.h>
          #include "ObjCFunc.h"
          @implementation myClass
          //Your objective c code here....
          -(void)hello:(int)num1
          {
          NSLog(@"Hello!!!!!!");
          }
          @end
          

          第 2 步

          现在我们将实现一个 c++ 函数来调用我们刚刚创建的目标 c 函数! 因此,为此我们将定义一个 .mm 文件及其对应的头文件(此处使用“.mm”文件,因为我们将能够在文件中使用 Objective C 和 C++ 编码)

          //头文件(我们称之为“ObjCCall.h”)

          #ifndef __test2__ObjCCall__
          #define __test2__ObjCCall__
          #include <stdio.h>
          class ObjCCall
          {
          public:
          static void objectiveC_Call(); //We define a static method to call the function directly using the class_name
          };
          #endif /* defined(__test2__ObjCCall__) */
          

          //对应的Objective C++文件(我们称之为“ObjCCall.mm”)

          #include "ObjCCall.h"
          #include "ObjCFunc.h"
          void ObjCCall::objectiveC_Call()
          {
          //Objective C code calling.....
          myClass *obj=[[myClass alloc]init]; //Allocating the new object for the objective C   class we created
          [obj hello:(100)];   //Calling the function we defined
          }
          

          第 3 步

          调用c++函数(实际上是调用目标c方法)

          #ifndef __HELLOWORLD_SCENE_H__
          #define __HELLOWORLD_SCENE_H__
          #include "cocos2d.h"
          #include "ObjCCall.h"
          class HelloWorld : public cocos2d::Layer
          {
          public:
          // there's no 'id' in cpp, so we recommend returning the class instance pointer
          static cocos2d::Scene* createScene();
          // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning  'id' in cocos2d-iphone
          virtual bool init();
          // a selector callback
          void menuCloseCallback(cocos2d::Ref* pSender);
          void ObCCall();  //definition
          // implement the "static create()" method manually
          CREATE_FUNC(HelloWorld);
          };
          #endif // __HELLOWORLD_SCENE_H__
          

          //最终通话

          #include "HelloWorldScene.h"
          #include "ObjCCall.h"
          USING_NS_CC;
          Scene* HelloWorld::createScene()
          {
          // 'scene' is an autorelease object
          auto scene = Scene::create();
          // 'layer' is an autorelease object
          auto layer = HelloWorld::create();
          // add layer as a child to scene
          scene->addChild(layer);
          // return the scene
          return scene;
          }
          // on "init" you need to initialize your instance
          bool HelloWorld::init()
          {
          //////////////////////////////
          // 1. super init first
          if ( !Layer::init() )
          {
              return false;
          }
          Size visibleSize = Director::getInstance()->getVisibleSize();
          Vec2 origin = Director::getInstance()->getVisibleOrigin();
          
          /////////////////////////////
          // 2. add a menu item with "X" image, which is clicked to quit the program
          //    you may modify it.
          
          // add a "close" icon to exit the progress. it's an autorelease object
          auto closeItem = MenuItemImage::create(
                                                 "CloseNormal.png",
                                                 "CloseSelected.png",
                                                 CC_CALLBACK_1(HelloWorld::menuCloseCallback,  this));
          
          closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,
                                      origin.y + closeItem->getContentSize().height/2));
          
          // create menu, it's an autorelease object
          auto menu = Menu::create(closeItem, NULL);
          menu->setPosition(Vec2::ZERO);
          this->addChild(menu, 1);
          
          /////////////////////////////
          // 3. add your codes below...
          
          // add a label shows "Hello World"
          // create and initialize a label
          
          auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
          
          // position the label on the center of the screen
          label->setPosition(Vec2(origin.x + visibleSize.width/2,
                                  origin.y + visibleSize.height - label- >getContentSize().height));
          // add the label as a child to this layer
          this->addChild(label, 1);
          // add "HelloWorld" splash screen"
          auto sprite = Sprite::create("HelloWorld.png");
          // position the sprite on the center of the screen
          sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 +     origin.y));
          // add the sprite as a child to this layer
          this->addChild(sprite, 0);
          this->ObCCall();   //first call
          return true;
          }
          void HelloWorld::ObCCall()  //Definition
          {
          ObjCCall::objectiveC_Call();  //Final Call  
          }
          void HelloWorld::menuCloseCallback(Ref* pSender)
          {
          #if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM ==   CC_PLATFORM_WINRT)
          MessageBox("You pressed the close button. Windows Store Apps do not implement a close    button.","Alert");
          return;
          #endif
          Director::getInstance()->end();
          #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
          exit(0);
          #endif
          }
          

          希望这行得通!

          【讨论】:

            【解决方案9】:

            @DawidDrozd 上面的回答非常好。

            我要补充一点。最新版本的 Clang 编译器抱怨在尝试使用他的代码时需要“桥接演员”。

            这似乎是合理的:使用蹦床会产生一个潜在的错误:由于 Objective-C 类是引用计数的,如果我们将它们的地址作为 void * 传递,如果类在回调仍然有效。

            解决方案 1) Cocoa 提供了 CFBridgingRetain 和 CFBridgingRelease 宏函数,它们可能会在 Objective-C 对象的引用计数中加减一。因此,我们应该小心使用多个回调,以释放与保留相同的次数。

            // C++ Module
            #include <functional>
            
            void cppFnRequiringCallback(std::function<void(void)> callback) {
                    callback();
            }
            
            //Objective-C Module
            #import "CppFnRequiringCallback.h"
            
            @interface MyObj : NSObject
            - (void) callCppFunction;
            - (void) myCallbackFn;
            @end
            
            void cppTrampoline(const void *caller) {
                    id callerObjC = CFBridgingRelease(caller);
                    [callerObjC myCallbackFn];
            }
            
            @implementation MyObj
            - (void) callCppFunction {
                    auto callback = [self]() {
                            const void *caller = CFBridgingRetain(self);
                            cppTrampoline(caller);
                    };
                    cppFnRequiringCallback(callback);
            }
            
            - (void) myCallbackFn {
                NSLog(@"Received callback.");
            }
            @end
            

            解决方案 2) 替代方法是使用等效的弱引用(即不更改保留计数),没有任何额外的安全性。

            Objective-C 语言提供了 __bridge 强制转换限定符来执行此操作(CFBridgingRetain 和 CFBridgingRelease 似乎分别是 Objective-C 语言结构 __bridge_retained 和 release 的薄 Cocoa 包装器,但 Cocoa 似乎没有 __bridge 的等效项) .

            所需的更改是:

            void cppTrampoline(void *caller) {
                    id callerObjC = (__bridge id)caller;
                    [callerObjC myCallbackFn];
            }
            
            - (void) callCppFunction {
                    auto callback = [self]() {
                            void *caller = (__bridge void *)self;
                            cppTrampoline(caller);
                    };
                    cppFunctionRequiringCallback(callback);
            }
            

            【讨论】:

            • 我不得不承认,我对解决方案 1 是否提供任何额外的安全性感到有些怀疑,尽管有所有的保留/释放戏剧。有一点是我们将self 的副本传递到闭包中,这可能会过时。另一点是不清楚这如何与自动引用计数交互以及编译器是否可以弄清楚发生了什么。在实践中,我没有设法在一个简单的单模块玩具示例中创建任何一个版本都失败的情况。
            猜你喜欢
            • 1970-01-01
            • 2023-03-29
            • 2013-12-23
            • 1970-01-01
            • 2014-11-13
            • 1970-01-01
            • 2011-05-26
            • 2010-09-13
            • 1970-01-01
            相关资源
            最近更新 更多