広告のSDKをアップデートしなければいけなかったので、久しぶりにAndroidのビルドをしようと思い、Android Studioをアップデートしてビルドしてみたら、案の定うまくいかない。cocos2dもアップデートしたり、他のSDKもアップデートして、色々エラーを取り除いて、最後に残ったのが、
undefined reference to non-virtual thunk to …
というエラー。みつからないのは、cocos2dのTextureProtocolクラスの仮想関数(のnon-virtual thunk)。このクラスはSpriteクラスで多重継承されていて、エラーが出るのは自分でSpriteクラスを派生させた独自クラス。
この部分のコードは前から変わってなく、変わったのはツールチェインだけだし、こんなのGCCのバグだろうと思うのだけど、とりあえずコンパイルを通したかったので、回避方法をみつけました。
まず、cocos2dのSpriteクラスを見ると次のように宣言されてます。
1 2 3 4 5 6 7 8 9 10 11 12 |
class CC_DLL Sprite : public Node, public TextureProtocol { : }; class CC_DLL TextureProtocol : public BlendProtocol { public: virtual ~TextureProtocol() {} virtual Texture2D* getTexture() const = 0; virtual void setTexture(Texture2D *texture) = 0; }; |
non-virtual thunkというのをGoogleで調べると「What is a non-virtual thunk?」という投稿がみつかって、そこに詳しく書いてあるんだけど、簡単に説明すると、TextureProtocolのポインタとしてSpriteクラスの仮想関数が呼ばれたときに、thisポインタを調整するための関数みたいです。コードで表すと
1 2 3 4 5 |
Texture2D* non_virtual_thunk_to_Sprite_getTexture(TextureProtocol* this) { Sprite* pSprite = static_cast<Sprite*>(this); return pSprite->Sprite::getTexture(); } |
という感じになるでしょうか。多重継承しているので、thisポインタをそのままSpriteとして使うことはできず、static_castして正しい位置を指すようにずらさないといけません。
cocos2dはスタティックライブラリとしてアーカイブされていて、それをアプリの.soにリンクするときに、このようなnon-virtual thunkがみつからなくなるようです。アプリ側はSpriteを継承したクラスはあるものの、TextureProtocolの関数はオーバーライドしていないので、本来ならSpriteのnon-virtual thunkをそのまま使えるはずだし、実際コンパイラはそれを使おうとしているけれど、エラーがでてしまうのです。
しかたがないので、アプリ側でSpriteを継承したクラスでも、TextureProtocolの関数をオーバーライドしてエラーを回避することにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class MySprite : public cocos2d::Sprite { public: : // undefined reference to non-virtual thunk to... を回避するために追加. virtual cocos2d::Texture2D* getTexture() const override; virtual void setTexture(cocos2d::Texture2D *texture) override; }; cocos2d::Texture2D* MySprite::getTexture() const { return cocos2d::Sprite::getTexture(); } void MySprite::setTexture(cocos2d::Texture2D *texture) { cocos2d::Sprite::setTexture(texture); } |
どう考えてもこんな関数追加するなんて無駄だし、やっぱりGCCのバグだよね?