連絡先 Hikwareホーム
Hikware.Tech
自分用の覚え書きをそのまま公開。参考程度にどうぞ。

C++からObjective-Cをたまに使いたい

公開日 2017/10/19
最終更新 2017/11/21
  • 拡張子が .mm の Objective-C++ ソースファイルには C++ のコードを書けるが、拡張子が .cpp の C++ ソースファイルには Objective-C のコードを書けないので何とかしたい。
  • マルチプラットフォームのためソースファイル名は .cpp にしておきたいが、必要な時にだけ Cocoa Touch の機能を使いたい。

.cpp ファイルを Objective-C++ としてコンパイルさせる方法

ファイル単位で指定

.cpp ファイルを選択後、File inspectorTypeObjective-C++ Source に変える。ソース内にプラグマとかで指示できると分かりやすいんだけど、そういうのはないぽい。

全てのソースをObjective-C++扱いでコンパイルさせる

ターゲットまたはプロジェクトの Build SettingsCompile Source AsAccording to File Type から Objective-C++ に変える。.cpp が大量にあるなら便利だけど、遅いとかデカくなるとかなんか罠がありそうで、あんまやりたくない。

.cpp ファイルはあくまで C++ としてコンパイルさせたい場合

 チームや宗教的な都合上、ファイル設定やコンパイル設定を変更する事が許されない場合は、クラスの宣言は純粋な C++ として書いて、Objective-C の機能を使うメソッドだけ .mm ファイルに隔離して実装するという手もある。いろいろ鬱陶しいけど。

MyClass.h      <- 純粋なC++クラスの宣言
MyClass.cpp    <- C++ だけで書けるメソッドはこっちに実装
MyClass.mm     <- Objective-C を使いたいメソッドはこっちに隔離
ObjCMethods.mm <- .mmが無駄に増えるのが嫌なら Objective-C の部分だけ寄せ集めたり(暴挙)

Objective-C のオブジェクトを C++ のクラスのメソッドに渡したり、クラスメンバとして保持したい

 Objective-C のオブジェクトも基本ポインタでやりとりしてるので、以下みたいにヘッダで前方宣言しておけばいけるんじゃね?と思ったけど、そもそも Objective-C のクラスは class でも struct でもないので純粋な C++ では前方宣言できない。

// 内容は教えないが MyClassA というクラスがある事だけ宣言
class MyClassA;

class MyClassB
{
private:
    // 内容が分からないので実体は作れないが、ポインタは保持できる
    MyClassA*    mClassA;

public:
    // メソッド引数にもできる
    void
    SetClassA(
        MyClassA* classA);
};

// Objective-C のソース内であれば、以下のように Objective-C の
// クラスも前方宣言できるらしいが、C++では当然エラー。
@class MyObjCClass;

 てことで一見無理そうなんだけど、どうしても C++ のクラス内に ViewController のポインタを保持したいケースがあったので、足掻いた結果以下のようになった。

#import <UIKit/UIKit.h>

// C++クラスにViewControllerを渡すためのラッパー
struct VCWrapper
{
    // weak にしないと循環参照になる。weak 超重要!
    __weak UIViewController*    vc;
};
#import "MyViewController.h"
#import "VCWrapper.h"

@implementation MyViewController
{
    // ViewController 内に C++ のクラスを保持して、実処理はそちらでやる
    MyCppClass    mCppClass;
    VCWrapper     mVcWrapper;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Objective-Cのクラスは直接渡せないので、ラッパーに包んで渡す。
    // ラッパーは使い終わるまで保持しとかないといけない。
    mVcWrapper.vc = self;
    mCppClass.SetOwnerViewController( &mVcWrapper );
}

@end
// このヘッダは純粋なC++なので、他の.cppソースからインクルードしても問題ない

// VCWrapper の中身は他のファイルには隠蔽
struct VCWrapper;

class MyCppClass
{
private:
    // ViewController はラッパーのポインタで保持
    VCWrapper*    mOwnerVC;

public:
    void
    SetOwnerViewController(
        VCWrapper* vc)
        { mOwnerVC = vc; }

    // 例: C++ ソースからViewControllerを閉じる
    void
    DismissViewController();
};
// このソースは .cpp だが、File inspector で Objective-C++ としてコンパイルさせる
// もしくは拡張子を .mm にする
#import "MyCppClass.h"
#import "VCWrapper.h"

// 例: C++ ソースからViewControllerを閉じる
void
MyCppClass::DismissViewController()
{
    // このファイル内では ViewController のメソッドも呼び出し可能
    [mOwnerVC->vc dismissViewControllerAnimated:true completion:nil];
}

 もっといい方法ないのかね。回りくどいだけで大したことはしてないんだけど、ラッパー内に保持するメンバを __weak にするのだけは忘れないように。処分されなくなるよ。

Objective-C や C++ でのコンパイルを判別するマクロ

【2017/11/21追記】
 C++ と Objective-C の両方からインクルードするヘッダ内に Objective-C のコードを書いたり import したい場合は以下の定義済みマクロで判定できる。

#ifdef __OBJC__
// Objective-C または Objective-C++ としてコンパイルした場合のみ有効。
// C++ からもインクルードするヘッダ内に Objective-C のコードを書いたり
// Foundation.h や UIKit.h などを import するならこの中で。
#endif

#ifdef __cplusplus
// Objective-C++ または C++ としてコンパイルした場合のみ有効。
// 例えば Swift との bridging header にも含めたいヘッダには
// C++ のコードは書けないので、これで無効化しないといけない。
#endif