使用C++与Cocoa代替Objective-C?

131

我希望编写使用C++和Cocoa框架的应用程序,因为苹果不再支持Carbon 64位。在Linux和Windows上,C++的实现似乎相当基础,但在Mac OS X上,需要额外的苹果特定代码(例如Obj-C包装器)。同时,苹果好像强制开发者使用Objective-C而非C++,尽管我可能是错的。

我试图找到一种在Mac上编写跨平台代码的方法。不想在Linux/Windows中使用C++编写代码,然后重写大量的Objective-C代码,这样非常低效。

是否有一种方法可以编写支持未来且受Xcode支持的C++代码? 如果可能的话,我如何在Xcode中混合使用C++和Objective-C?谢谢。

7个回答

116

你不能完全用C++编写Cocoa应用程序。 Cocoa 在其许多核心技术中(例如键值绑定、委托 (Cocoa 风格) 和目标-动作模式)严重依赖于 Objective-C 的后期绑定能力。这些后期绑定要求使得在像 C++ 这样的编译时绑定和类型化语言中实现 Cocoa API 变得非常困难ⁱ。当然,你可以编写在OS X上运行的纯C++应用程序。只是它不能使用Cocoa API。

因此,如果您想在其他平台上编写C++应用程序并与基于Cocoa的应用程序共享代码,则有两个选择。第一种是在C++中编写模型层,而在Cocoa中编写GUI。这是一种常见的方法,被一些非常大的应用程序(包括Mathematica)使用。可以不更改C++代码(您不需要在OS X上编写或编译C++的“时髦”苹果扩展)。你的控制器层可能会使用Objective-C ++ (也许是你提到的“时髦”苹果扩展)。Objective-C++ 是 C++ 的超集,就像 Objective-C 是 C 的超集一样。在 Objective-C++ 中,您可以从 C++ 函数中进行 objc 样式的消息传递调用(如 [some-objc-object callMethod];)。反之,您可以在 ObjC 代码中调用 C++ 函数,例如:

@interface MyClass {
    MyCPPClass *cppInstance;
}
@end

@implementation MyClass
- (id)init {
    if(self = [super init]) {
        cppInstance = new MyCPPClass();
    }
    return self;
}
- (void) dealloc {
    if(cppInstance != NULL) delete cppInstance;
    [super dealloc];
}
- (void)callCpp {
    cppInstance->SomeMethod();
}
@end

您可以在Objective-C语言指南中了解更多关于Objective-C++的内容。然后,视图层可以是纯Objective-C。
第二个选项是使用跨平台C++工具包。Qt 工具包可能很适合您的需求。跨平台工具包通常被Mac用户所鄙视,因为它们无法完全正确地获取所有外观和感觉细节,而Mac用户期望Mac应用程序的UI有精致的外观。然而,Qt做得出奇地好,并且根据受众和您的应用程序使用情况而定,这可能已经足够了。此外,您将失去一些OS X特定技术,例如核心动画和一些QuickTime功能,尽管在Qt API中有近似替代品。正如您指出的那样,Carbon将不会被移植到64位。由于Qt是基于Carbon API实现的,Trolltech/Nokia不得不将Qt移植到Cocoa API上,以使其在OS X上支持64位。我的理解是,Qt的下一个版本(目前处于发布候选状态)将完成此转换,并在OS X上支持64位。如果您有兴趣将C++和Cocoa API集成起来,您可能需要查看Qt 4.5的源代码。

ⁱ 有一段时间,苹果公司将Cocoa API提供给Java使用,但是这个桥接需要大量手动调整,并且无法处理像上面描述的键值绑定等更高级技术。目前,像Python、Ruby等动态类型、运行时绑定的语言是编写没有Objective-C的Cocoa应用程序的唯一真正选择(虽然当然这些桥接在底层使用Objective-C)。


我目前正在尝试移植我的小型Ogre3D应用程序,看起来非常痛苦。苹果是在试图将每个人都转换为Objc,还是这真的是一种特性? - jokoon
问题的实际答案是 https://dev59.com/J3RB5IYBdhLWcg3wv5s_#14083212。 - Michael Kruglos

71

虽然听起来有点傻,但实际上我们可以编写纯C++代码为Mac OS X创建GUI,但必须链接到Cocoa框架。

/*
 * test1.cpp
 * This program shows how to access Cocoa GUI from pure C/C++
 * and build a truly functional GUI application (although very simple).
 * 
 * Compile using:
 *   g++ -framework Cocoa -o test1 test1.cpp
 *
 * that will output 'test1' binary.
 */


#include <CoreFoundation/CoreFoundation.h>
#include <objc/objc.h>
#include <objc/objc-runtime.h>
#include <iostream>

extern "C" int NSRunAlertPanel(CFStringRef strTitle, CFStringRef strMsg,
                               CFStringRef strButton1, CFStringRef strButton2, 
                               CFStringRef strButton3, ...);


int main(int argc, char** argv)
{
    id app = NULL;
    id pool = (id)objc_getClass("NSAutoreleasePool");
    if (!pool)
    {
        std::cerr << "Unable to get NSAutoreleasePool!\nAborting\n";
        return -1;
    }
    pool = objc_msgSend(pool, sel_registerName("alloc"));
    if (!pool)
    {
        std::cerr << "Unable to create NSAutoreleasePool...\nAborting...\n";
        return -1;
    }
    pool = objc_msgSend(pool, sel_registerName("init"));

    app = objc_msgSend((id)objc_getClass("NSApplication"),
                       sel_registerName("sharedApplication"));

    NSRunAlertPanel(CFSTR("Testing"),
                    CFSTR("This is a simple test to display NSAlertPanel."),
                    CFSTR("OK"), NULL, NULL);

    objc_msgSend(pool, sel_registerName("release"));
    return 0;
}

17
这很棒。有更复杂的例子吗?例如,打开一个NSWindow? - geometrian
test1.cpp: 在函数 'int main(int, char**)' 中: test1.cpp:26:48: 错误: 无法将 'Class {aka objc_class*}' 转换为 'id {aka objc_object*}' 进行初始化 id pool = objc_getClass("NSAutoreleasePool"); ^ test1.cpp:41:61: 错误: 无法将 'Class {aka objc_class*}' 转换为 'id {aka objc_object*}',作为参数传递给 'objc_object* objc_msgSend(id, SEL, ...)' sel_registerName("sharedApplication")); ^ - Jichao
7
请参阅Clang与内部Objective-C类型的兼容性@Jichao - 修复方法很简单:使用(id)objc_getClass替换objc_getClass即可。 - Dmitry Isaev
我该如何使用std :: string设置例如警报面板的标题? 我尝试使用c_str()等方法,但都没有成功... - user10699646
2
在macOS Catalina中,这个不再编译。 - user4396006
显示剩余2条评论

18

是的,你可以直接使用C++(即在*.cpp文件中编写),甚至可以在*.mm文件中混合使用C++和Objective-C(标准的Objective-C代码存储在*.m文件中)。

当然,你仍然需要使用Objective-C来创建用户界面,并为你的C++对象创建Objective-C包装器。另一个选择是切换到Qt,它是一个支持Windows、Mac OS X和Linux的C++框架,并将在下一个版本4.5中发布LGPL许可证。


24
请注意,如果您使用Qt,您的应用程序将表现不佳。基于Qt的应用程序看起来和感觉不像原生的Mac应用程序。(例如,参见Google Earth。) - Peter Hosey
17
Peter说:“这完全不正确。基于Qt的应用程序可以看起来和感觉与本机Mac应用程序完全相同,只需要进行每个平台的微调,这比在每个平台上编写本机GUI容易得多。” - Mike McQuaid
13
Mike,你的信息不准确。Qt-based应用在Mac上有其他缺陷之外,完全没有使用原生控件,而是由Qt库自己绘制。这意味着Qt应用程序在2D渲染方面没有获得任何硬件加速,它们不会与苹果对标准控件所做的UI更改保持同步,并且除非你自己重新发明那些轮子,否则Qt应用无法提供ADA合规性或可编程能力。换句话说,在Mac上不要尝试发布Qt应用程序。谷歌可以这样做,但你不能。 - NSResponder
13
他们确实使用了原生控件,这就是为什么Qt有Cocoa和Carbon版本。它还存在其他问题,但很多人都在Mac上发布Qt应用程序,并且它们可以正常工作(稍加调整后甚至可以完美运行)。 - Mike McQuaid
2
仅仅使用本地控件并不意味着它看起来和感觉像本地应用程序。使应用程序具有本地感觉的是每个操作系统的差异。如果您调整应用程序以在特定平台上感受到,那么在另一个平台上它就不会感受到本地。而且,在一次抽象层上微调小行为总是比在本地层上进行更困难。 - eonil
显示剩余5条评论

9

是的,您可以混合使用它们。

您需要使用Objective-C直接操作GUI对象并从中接收通知。

如果将这些Objective-C对象放在.mm文件中而不是纯Objective-C .m文件中,则可以直接调用C ++逻辑。请注意,您可能会看到(更多)旧建议建议使用大写.M来表示Objective-C ++,但这非常不可靠,并且很可能会使您以及编译器感到困惑。

您不需要包装每个C ++对象,但是您的Objective-C代码将需要包含指向它们的指针。

Apple不再发布任何显示如何执行此操作的示例。

Peter Steinberger在Realm托管了一段很棒的视频[Objective] C ++:What Could Possibly Go Wrong?我强烈推荐任何仍在使用Objective-C ++的人观看,您可以快速浏览副本。


@SteveS,你的链接也坏了。 - fferri
@fferi - Steinberger的链接已经修复。Carbon Cocoa Integration是来自2007年developer.apple.com的,Apple已将其删除。这表明您真的不应该使用Carbon API编写新代码。此时,即使是维护现有使用Carbon的代码也存在风险。如果您需要混合C++/Objective C,请参考此问题的已接受答案或此答案,但您不应该使用Carbon。话虽如此,在这里:Carbon-Cocoa-Integration - SteveS

4
如果您只想使用纯粹的C++,那么这绝对是支持的,与其他平台没有任何区别。Xcode甚至在“文件”>“新建项目”>“命令行实用程序”>“C++工具”下提供了一个模板。此外,一些流行的开源库(libcurl、libxml2、sqlite等)已经包含在OS X中,并且可供动态链接。如果您不想使用Cocoa或任何特定于Apple的东西,那么您也不必使用。
如果您确实想在应用程序的某些部分中使用Cocoa,请查看Objective-C++。您可以通过将其扩展名更改为.mm,或在Xcode中右键单击文件并选择“获取信息”>“常规”,然后更改文件类型为sourcecode.cpp.objcpp,在同一文件中混合使用C++和Objective-C。如果您有一个.cpp文件,在其中要在Mac特定的#ifdef中使用Objective-C,则第二个选项很有用。

2
顺便提一下,最近版本的Xcode(4.x和5.x)已经取消了(非常有用的)C++模板。 - Jay

2

虽然这是一个几年前的问题...

我曾经尝试过制作一些Cocoa类的C++包装器

这是一次非常好的经历。C++提供了比Objective-C更好的类型安全性,并且让我写的代码更少。但编译时间和内存安全性却更差。虽然有可能解决,但某些基于动态特性的处理并不容易。我觉得在C++上处理这个问题没有意义。

无论如何,我的项目最终因Swift的宣布而被放弃了。Swift清除了我最初想使用C++的所有理由,并提供了更多更好的功能。


C++比Swift快得多。那个原因呢? - Tom

0

如果你正在编写一个纯粹的图形应用程序,即你使用代码绘制一切,请考虑使用openFrameworks。它是一个基于C/C++的开源图形编程语言。它有插件,允许人们扩展该语言。他们还有为iPhone准备的插件。我相信它带有库和XCode项目,可以帮助你编译适用于iPhone和iPod touch的应用程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接