24 January 2016
版权声明:本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名elloop(包含链接)

#前言

本文总结了在windows上使用cocos compile命令编译cocos2d-x安卓apk的基本用法,以及记录一个使用NDK-r9d(gcc 4.8)编译C++11的hash_map遇到的一个问题: error: invalid use of incomplete type 'struct std::hash<MessageType>'

cocos compile 基本用法, 编译debug版本的cpp-tests.

从2.x到3.x,cocos的辅助工具做的越来越完善了,2.x刚开始的时候编译apk是比较费劲的,除了最基本的安装JDK, NDK, Android SDK, Ant还要装cygwin,自己配环境变量,执行打包脚本等等。我最近使用的是cocos2d-x官方的github仓库中的默认v3分支最新代码(3.10), 以编译cpp-tests工程为android apk为例,使用最新的打包方式:cocos compile.

基本的环境准备

如今的cocos,大量使用了python作为其辅助工具,因此第一步就是装好python,推荐比较经典的2.7.x版本。

装好python之后就可以到引擎的根目录,执行setup.py来初始化cocos环境了,它会引导你配置好cocos命令行工具执行所需要的环境变量和工具。其中包括:

Setting up cocos2d-x...
->Check environment variable COCOS_CONSOLE_ROOT
  ->Search for environment variable COCOS_CONSOLE_ROOT...
    ->COCOS_CONSOLE_ROOT is found : D:\codes\cocos2d-x\tools\cocos2d-console\bin

->Check environment variable COCOS_X_ROOT
  ->Search for environment variable COCOS_X_ROOT...
    ->COCOS_X_ROOT is found : D:\codes

->Check environment variable COCOS_TEMPLATES_ROOT
  ->Search for environment variable COCOS_TEMPLATES_ROOT...
    ->COCOS_TEMPLATES_ROOT is found : D:\codes\cocos2d-x\templates

->Configuration for Android platform only, you can also skip and manually edit your environment variables

->Check environment variable NDK_ROOT
  ->Search for environment variable NDK_ROOT...
    ->NDK_ROOT is found : D:\Programs\android-ndk-r9d\

->Check environment variable ANDROID_SDK_ROOT
  ->Search for environment variable ANDROID_SDK_ROOT...
    ->ANDROID_SDK_ROOT is found : D:\Programs\adt-bundle-windows-x86_64-20131030\sdk

->Check environment variable ANT_ROOT
  ->Search for environment variable ANT_ROOT...
    ->ANT_ROOT is found : D:\programs\apache-ant-1.9.4\bin\

其中几个常用SDK和NDK的下载和安装不再细说。在配置好之后,能看到像我上面这样的输出就ok了,相对于2.x中要做的工作,这样的引导是安装已经很简单了。

在命令行执行打包命令

配置好环境之后,打包就非常简单了。对于cocos自带的cpp-tests项目,打包apk有两种操作方法:

方法1

在引擎根目录/build/目录下,执行python android_build.py cpp-tests即可。建议在cmd窗口中操作,可以看到真个过程,包括异常退出等情况。关于android_build.py的更多用法请参考其文件内容,里面有usage说明。

方法2

在引擎根目录/tests/cpp-tests/目录,执行cocos命令:

cocos compile -p android

其实,两种方法都是使用了cocos compile这个命令行工具。关于其完整的用法请在cmd中输入cocos compile -h即可查看帮助信息。第一种方式中的脚本android_build.py其实是使用-s参数来调用cocos compile命令,-s选项指定了工程的目录地址。即可。自己打开瞧瞧它的内容就知道了。很简单。

自己创建的项目打包apk

使用cocos new命令创建的工程如何打包成apk?

也很简单,跟cpp-tests是一样的过程,也是使用cocos compile命令。在执行命令之前,比cpp-tests多的一步操作是,添加自己写的C++源代码文件名称和包含路径到Android.mk.

假设项目名字叫”Dog”

第一步,添加文件包含路径和cpp文件名到Dog/proj.android/jni/Android.mk

例如下面的Android.mk文件是我的游戏目录下/proj.android/jni/Android.mk文件,其中那一堆cpp就是自己写的cpp源代码,要参与到二进制编译的cpp都要加进来。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

$(call import-add-path,$(LOCAL_PATH)/../../cocos2d)
$(call import-add-path,$(LOCAL_PATH)/../../cocos2d/external)
$(call import-add-path,$(LOCAL_PATH)/../../cocos2d/cocos)

LOCAL_MODULE := cocos2dcpp_shared

LOCAL_MODULE_FILENAME := libcocos2dcpp

# 从hellocpp/main.cpp这行往下,是自己添加的cpp文件
LOCAL_SRC_FILES := hellocpp/main.cpp \
                   ../../Classes\AppDelegate.cpp \
                   ../../Classes\CCDirector.cpp \
                   ../../Classes\customs\FlowLayout.cpp \
                   ../../Classes\customs\Menu.cpp \
                   ../../Classes\data_models\TestDataCenter.cpp \
                   ../../Classes\LogicDirector.cpp \
                   ../../Classes\message\Message.cpp \
                   ../../Classes\message\MessageCenter.cpp \
                   ../../Classes\PageManager.cpp \
                   ../../Classes\pages\CameraTest.cpp \
                   ../../Classes\pages\CCBPage.cpp \
                   ../../Classes\pages\CCBTestPage.cpp \
                   ../../Classes\pages\LittleTouchPage.cpp \
                   ../../Classes\pages\MainPage.cpp \
                   ../../Classes\pages\NodeTestPage.cpp \
                   ../../Classes\pages\RootPage.cpp \
                   ../../Classes\pages\ScrollviewTestPage.cpp \
                   ../../Classes\pages\SuperPage.cpp \
                   ../../Classes\pages\TestEntranceLayer.cpp \
                   ../../Classes\pages\TouchTestPage.cpp \
                   ../../Classes\util\CocosWindow.cpp \
                   ../../Classes\util\cocos_util.cpp \
                   ../../Classes\util\DrawNode3D.cpp \
                   ../../Classes\util\StringUtil.cpp \
                   ../../Classes\pages\ShaderTest.cpp 
				   

# 文件的包含路径
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes

# _COCOS_HEADER_ANDROID_BEGIN
# _COCOS_HEADER_ANDROID_END


LOCAL_STATIC_LIBRARIES := cocos2dx_static

# _COCOS_LIB_ANDROID_BEGIN
# _COCOS_LIB_ANDROID_END

include $(BUILD_SHARED_LIBRARY)

$(call import-module,.)

# _COCOS_LIB_IMPORT_ANDROID_BEGIN
# _COCOS_LIB_IMPORT_ANDROID_END

2. 在Dog/proj.android/目录下执行:cocos compile -p android

通过这两部就完成了自己创建项目的打包apk操作,可以注意到,比较复杂的就是第一步,在把自己的cpp文件加到Android.mk的时候,尤其是当你的cpp放的到处都是,像我的那样分散在多个子目录中。为了这一步的自动化,可以自己写个脚本。我这里分享一个很简单的小脚本,它可以遍历子目录,记录下所有的cpp文件。其实就是一个简单的类似unix命令tree的小脚本。

tree.py: 递归遍历子目录,统计所有.cpp文件,并写入完整路径到cpps.txt

# coding=utf-8

import os


def get_dir_tree(begin_dir, coll):
    files = os.listdir(begin_dir)
    for file_name in files:
        full_path = os.path.join(begin_dir, file_name)
        if os.path.isdir(full_path):
            get_dir_tree(full_path, coll)
        else:
            coll.append(full_path)


if "__main__" == __name__:
    file_tree = []
    get_dir_tree(os.getcwd(), file_tree)

    # write .cpp file names into file
    with open("cpps.txt", "w") as f:
        for name in file_tree:
            if str(name).endswith(".cpp"):
                f.write(name + " \\\n")

把这个tree.py脚本丢到: 项目根目录/Classes/目录下,执行即可生成一个cpps.txt, 里面记录了所有的cpp文件路径,然后按列选择/Classes/..../xxxx.cpp \, 复制粘贴到Android.mk即可。

cpps.txt

D:\codes\3.9\PlayingWithCocos3D\Classes\AppDelegate.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\CCDirector.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\cocos_src_modified\CCNodeLoader.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\customs\FlowLayout.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\customs\Menu.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\data_models\TestDataCenter.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\HelloWorldScene.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\LogicDirector.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\message\Message.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\message\MessageCenter.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\PageManager.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\CameraTest.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\CCBPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\CCBTestPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\LittleTouchPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\MainPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\NodeTestPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\RootPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\ScrollviewTestPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\ShaderTest.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\SuperPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\TestEntranceLayer.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\TouchTestPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\util\CocosWindow.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\util\cocos_util.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\util\DrawNode3D.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\util\StringUtil.cpp \

C++11中的强类型枚举(scoped enum)作为hash表键值导致的编译错误

我的cocos代码中使用了一个强类型枚举作为键值的unordered_map, 如下:

enum class MessageType
{
    kMessageTypeChangePage = 0,
    kMessageTypePushPage,
    kMessageTypePopPage,
    kMessageTypeChangeBackground,
};

class MessageCenter : public Singleton<MessageCenter> {

    // ......

private:
    typedef std::vector<Message*>                           MessageQueue;
    MessageQueue                                            messages_;

    typedef std::set<PriorityHandler*>                      HandlerQueue;
    typedef std::unordered_map<MessageType, HandlerQueue>   HandlerMap;
    HandlerMap                                              handlerMap_;
};

其中的HandlerMap类型就是以enum class MessageType来作为键值的hash表,在windows上我使用visual studio 2013编译项目,顺利通过。

编译apk时就出问题了。在使用cocos compile -p android命令,使用NDK对上面的代码进行编译的时候,会发现如下错误:

In file included from D:/programs/android-ndk-r9d/sources/cxx-stl/gnu-libstdc++/4.8/include/bits/hashtable.h:35:0,
                 from D:/programs/android-ndk-r9d/sources/cxx-stl/gnu-libstdc++/4.8/include/unordered_map:47,
                 from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../renderer/CCTexture2D.h:32,
                 from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../base/CCProtocols.h:34,
                 from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../2d/CCNode.h:34,
                 from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../2d/CCScene.h:32,
                 from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../base/CCDirector.h:37,
                 from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../base/CCAsyncTaskPool.h:29,
                 from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../cocos2d.h:41,
                 from jni/../../Classes/util/cocos_util.h:3,
                 from jni/../../Classes/cocos_include.h:4,
                 from jni/../../Classes/message/MessageCenter.h:4,
                 from jni/../../Classes\message\MessageCenter.cpp:1:
D:/programs/android-ndk-r9d/sources/cxx-stl/gnu-libstdc++/4.8/include/bits/hashtable_policy.h:1082:53: error: invalid use of incomplete type 'struct std::hash<MessageType>'
       using __ebo_h1 = _Hashtable_ebo_helper<1, _H1>;
                                                     ^

编译器抱怨说 : struct std::hash是不完整类型。它为什么会这样认为?这是因为hash表的定义需要一个hash函数,对于内置的数据类型,C++标准要求标准库要提供预定义的hash函数,在``头文件里可以找到内置数据类型的hash函数定义:

// std::hash 
// Defined in <funtional>
template<> struct hash<bool>;
template<> struct hash<char>;
template<> struct hash<signed char>;
template<> struct hash<unsigned char>;
template<> struct hash<char16_t>;
template<> struct hash<char32_t>;
template<> struct hash<wchar_t>;
template<> struct hash<short>;
template<> struct hash<unsigned short>;
template<> struct hash<int>;
template<> struct hash<unsigned int>;
template<> struct hash<long>;
template<> struct hash<long long>;
template<> struct hash<unsigned long>;
template<> struct hash<unsigned long long>;
template<> struct hash<float>;
template<> struct hash<double>;
template<> struct hash<long double>;
template< class T > struct hash<T*>;

而对于enum class类型C++标准并没有要求必须提供。windows上VC++编译器应该是提供了enum class的hash函数,因此编译能够通过。那么对于gcc编译器,要想解决这个问题就需要自己定义hash函数,针对我的enum class MessageType。

enum class MessageType
{
    kMessageTypeChangePage = 0,
    kMessageTypePushPage,
    kMessageTypePopPage,
    kMessageTypeChangeBackground,
};

namespace std 
{
    template <>
    struct hash<MessageType>
    {
        typedef MessageType     argument_type;
        typedef size_t          return_type;

        return_type operator() (const argument_type &arg) const
        {
            return static_cast<return_type>(arg);
        }
    };
}

为了快点通过编译在android上看到我的游戏测试结果,我定义了一个很简单的hash函数,直接把enum的值转为size_t。

有了这个hash函数,NDK编译通过了并且打包成功。


作者水平有限,对相关知识的理解和总结难免有错误,还望给予指正,非常感谢!

在这里也能看到这篇文章:github博客, CSDN博客, 欢迎访问



分享到