From cc0d982f72a584f17774c0b1e022491cf45fc610 Mon Sep 17 00:00:00 2001 From: Jonathan De Wachter Date: Sun, 22 Sep 2013 19:15:13 +0200 Subject: [PATCH] [Android] Wrote a native activity acting as a bootstrap A current limitation prevents one library from depending on shared libraries. As we have legal issues here (LGPL wants us to use shared libs of OpenAL-Soft and libsndfile), we're forced to use this homemade native activity which will manually load our shared libraries. --- CMakeLists.txt | 5 + cmake/Macros.cmake | 12 +++ extlibs/android/extlibs/Android.mk | 12 --- src/SFML/Android.mk | 7 ++ src/SFML/Main/CMakeLists.txt | 7 ++ src/SFML/Main/SFMLActivity.cpp | 166 +++++++++++++++++++++++++++++ 6 files changed, 197 insertions(+), 12 deletions(-) create mode 100644 src/SFML/Main/SFMLActivity.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ee99975c..1bca3973 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,11 @@ if(ANDROID) # we install libs in a subdirectory named after the ABI (lib/mips/*.so) set(LIB_SUFFIX "/${ANDROID_ABI}") + + # this is a workaround to compile sfml-activity without stlport_shared as dependency + # we save the original compilation command line to restore it later in Macro.cmake + set(CMAKE_CXX_CREATE_SHARED_LIBRARY_WITH_STLPORT ${CMAKE_CXX_CREATE_SHARED_LIBRARY}) + set(CMAKE_CXX_CREATE_SHARED_LIBRARY_WITHOUT_STLPORT " -o ") endif() # define SFML_STATIC if the build type is not set to 'shared' diff --git a/cmake/Macros.cmake b/cmake/Macros.cmake index 7c3db2f4..9966c34c 100644 --- a/cmake/Macros.cmake +++ b/cmake/Macros.cmake @@ -97,6 +97,18 @@ macro(sfml_add_library target) set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES) endif() + # sfml-activity library is our bootstrap activity and must not depend on stlport_shared + # (otherwise Android will fail to load it) + if (ANDROID) + if (${target} MATCHES "sfml-activity") + set_target_properties(${target} PROPERTIES COMPILE_FLAGS -fpermissive) + set_target_properties(${target} PROPERTIES LINK_FLAGS "-landroid -llog") + set(CMAKE_CXX_CREATE_SHARED_LIBRARY ${CMAKE_CXX_CREATE_SHARED_LIBRARY_WITHOUT_STLPORT}) + else() + set(CMAKE_CXX_CREATE_SHARED_LIBRARY ${CMAKE_CXX_CREATE_SHARED_LIBRARY_WITH_STLPORT}) + endif() + endif() + # link the target to its external dependencies if(THIS_EXTERNAL_LIBS) target_link_libraries(${target} ${THIS_EXTERNAL_LIBS}) diff --git a/extlibs/android/extlibs/Android.mk b/extlibs/android/extlibs/Android.mk index 3d99f41e..f0b3dec6 100644 --- a/extlibs/android/extlibs/Android.mk +++ b/extlibs/android/extlibs/Android.mk @@ -16,32 +16,24 @@ include $(CLEAR_VARS) LOCAL_MODULE := ogg LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libogg.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include -TARGET_ARCH_ABI := armeabi armeabi-v7a x86 - include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := flac LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libFLAC.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include -TARGET_ARCH_ABI := armeabi armeabi-v7a x86 - include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := vorbis LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libvorbis.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include -TARGET_ARCH_ABI := armeabi armeabi-v7a x86 - include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := vorbisenc LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libvorbisenc.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include -TARGET_ARCH_ABI := armeabi armeabi-v7a x86 - include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) @@ -49,8 +41,6 @@ LOCAL_MODULE := sndfile LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libsndfile.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_SHARED_LIBRARIES := ogg flac vorbis vorbisenc -TARGET_ARCH_ABI := armeabi armeabi-v7a x86 - include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) @@ -58,6 +48,4 @@ LOCAL_MODULE := openal LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libopenal.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_SHARED_LIBRARIES := ogg flac vorbis vorbisenc -TARGET_ARCH_ABI := armeabi armeabi-v7a x86 - include $(PREBUILT_SHARED_LIBRARY) diff --git a/src/SFML/Android.mk b/src/SFML/Android.mk index a5583746..62dfda6b 100644 --- a/src/SFML/Android.mk +++ b/src/SFML/Android.mk @@ -55,4 +55,11 @@ LOCAL_SHARED_LIBRARIES := sfml-system sfml-window include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := sfml-activity +LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libsfml-activity.so +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include + +include $(PREBUILT_SHARED_LIBRARY) + $(call import-module,sfml/extlibs) diff --git a/src/SFML/Main/CMakeLists.txt b/src/SFML/Main/CMakeLists.txt index d4561de6..84070cde 100644 --- a/src/SFML/Main/CMakeLists.txt +++ b/src/SFML/Main/CMakeLists.txt @@ -28,3 +28,10 @@ set_target_properties(sfml-main PROPERTIES FOLDER "SFML") # setup the install rule install(TARGETS sfml-main ARCHIVE DESTINATION lib${LIB_SUFFIX} COMPONENT devel) + +# because of a current limitation on Android (which prevents one library +# from depending on shared libraries), we need a boostrap activity which +# will load our shared libraries manually +if(ANDROID) + sfml_add_library(sfml-activity SOURCES ${PROJECT_SOURCE_DIR}/src/SFML/Main/SFMLActivity.cpp) +endif() diff --git a/src/SFML/Main/SFMLActivity.cpp b/src/SFML/Main/SFMLActivity.cpp new file mode 100644 index 00000000..2db09ba5 --- /dev/null +++ b/src/SFML/Main/SFMLActivity.cpp @@ -0,0 +1,166 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2013 Jonathan De Wachter (dewachter.jonathan@gmail.com) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_INFO, "sfml-activity", __VA_ARGS__)) + +void* loadLibrary(const char* libraryName, JNIEnv* lJNIEnv, jobject& ObjectActivityInfo) +{ + // Find out the absolute path of the library + jclass ClassActivityInfo = lJNIEnv->FindClass("android/content/pm/ActivityInfo"); + jfieldID FieldApplicationInfo = lJNIEnv->GetFieldID(ClassActivityInfo, "applicationInfo", "Landroid/content/pm/ApplicationInfo;"); + jobject ObjectApplicationInfo = lJNIEnv->GetObjectField(ObjectActivityInfo, FieldApplicationInfo); + + jclass ClassApplicationInfo = lJNIEnv->FindClass("android/content/pm/ApplicationInfo"); + jfieldID FieldNativeLibraryDir = lJNIEnv->GetFieldID(ClassApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;"); + + jobject ObjectDirPath = lJNIEnv->GetObjectField(ObjectApplicationInfo, FieldNativeLibraryDir); + + jclass ClassSystem = lJNIEnv->FindClass("java/lang/System"); + jmethodID StaticMethodMapLibraryName = lJNIEnv->GetStaticMethodID(ClassSystem, "mapLibraryName", "(Ljava/lang/String;)Ljava/lang/String;"); + + jstring LibNameObject = lJNIEnv->NewStringUTF(libraryName); + jobject ObjectName = lJNIEnv->CallStaticObjectMethod(ClassSystem, StaticMethodMapLibraryName, LibNameObject); + + jclass ClassFile = lJNIEnv->FindClass("java/io/File"); + jmethodID FileConstructor = lJNIEnv->GetMethodID(ClassFile, "", "(Ljava/lang/String;Ljava/lang/String;)V"); + jobject ObjectFile = lJNIEnv->NewObject(ClassFile, FileConstructor, ObjectDirPath, ObjectName); + + // Get the library absolute path and convert it + jmethodID MethodGetPath = lJNIEnv->GetMethodID(ClassFile, "getPath", "()Ljava/lang/String;"); + jstring javaLibraryPath = static_cast(lJNIEnv->CallObjectMethod(ObjectFile, MethodGetPath)); + const char* libraryPath = lJNIEnv->GetStringUTFChars(javaLibraryPath, NULL); + + // Manually load the library + void * handle = dlopen(libraryPath, RTLD_NOW | RTLD_GLOBAL); + if (!handle) + { + LOGE("dlopen(\"%s\"): %s", libraryPath, dlerror()); + exit(1); + } + + // Release the Java string + lJNIEnv->ReleaseStringUTFChars(THESTRING, pathStr); + + return handle; +} + +void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize) +{ + // Before we can load a library, we need to find out its location. As + // we're powerless here in C/C++, we need the JNI interface to communicate + // with the attached Java virtual machine and perform some Java calls in + // order to retrieve the absolute path of our libraries. + // + // Here's the snippet of Java code it performs: + // -------------------------------------------- + // ai = getPackageManager().getActivityInfo(getIntent().getComponent(), PackageManager.GET_META_DATA); + // File libraryFile = new File(ai.applicationInfo.nativeLibraryDir, System.mapLibraryName(libname)); + // String path = libraryFile.getPath(); + // + // With libname being the library name such as "jpeg". + + // Initialize JNI + jint lResult; + jint lFlags = 0; + + JavaVM* lJavaVM = activity->vm; + JNIEnv* lJNIEnv = activity->env; + + JavaVMAttachArgs lJavaVMAttachArgs; + lJavaVMAttachArgs.version = JNI_VERSION_1_6; + lJavaVMAttachArgs.name = "NativeThread"; + lJavaVMAttachArgs.group = NULL; + + // Attach the current thread to the JAva virtual machine + lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs); + + if (lResult == JNI_ERR) { + LOG("Couldn't attach the current thread to the Java virtual machine"); + exit(1); + } + + // Retrieve the NativeActivity + jobject ObjectNativeActivity = activity->clazz; + jclass ClassNativeActivity = lJNIEnv->GetObjectClass(ObjectNativeActivity); + + // Retrieve the ActivityInfo + jmethodID MethodGetPackageManager = lJNIEnv->GetMethodID(ClassNativeActivity, "getPackageManager", "()Landroid/content/pm/PackageManager;"); + jobject ObjectPackageManager = lJNIEnv->CallObjectMethod(ObjectNativeActivity, MethodGetPackageManager); + + jmethodID MethodGetIndent = lJNIEnv->GetMethodID(ClassNativeActivity, "getIntent", "()Landroid/content/Intent;"); + jobject ObjectIntent = lJNIEnv->CallObjectMethod(ObjectNativeActivity, MethodGetIndent); + + jclass ClassIntent = lJNIEnv->FindClass("android/content/Intent"); + jmethodID MethodGetComponent = lJNIEnv->GetMethodID(ClassIntent, "getComponent", "()Landroid/content/ComponentName;"); + + jobject ObjectComponentName = lJNIEnv->CallObjectMethod(ObjectIntent, MethodGetComponent); + + jclass ClassPackageManager = lJNIEnv->FindClass("android/content/pm/PackageManager"); + + //jfieldID FieldGET_META_DATA = lJNIEnv->GetStaticFieldID(ClassPackageManager, "GET_META_DATA", "L"); + //jobject GET_META_DATA = lJNIEnv->GetStaticObjectField(ClassPackageManager, FieldGET_META_DATA); + // getActivityInfo(getIntent().getComponent(), PackageManager.GET_META_DATA) -> ActivityInfo object + jmethodID MethodGetActivityInfo = lJNIEnv->GetMethodID(ClassPackageManager, "getActivityInfo", "(Landroid/content/ComponentName;I)Landroid/content/pm/ActivityInfo;"); + + // todo: do not hardcode the GET_META_DATA integer value but retrieve it instead + jobject ObjectActivityInfo = lJNIEnv->CallObjectMethod(ObjectPackageManager, MethodGetActivityInfo, ObjectComponentName, (jint)128); + + // Load our libraries in reverse order + loadLibrary("stlport_shared", lJNIEnv, ObjectActivityInfo); + loadLibrary("sfml-system", lJNIEnv, ObjectActivityInfo); + loadLibrary("sfml-window", lJNIEnv, ObjectActivityInfo); + loadLibrary("jpeg", lJNIEnv, ObjectActivityInfo); + loadLibrary("freetype", lJNIEnv, ObjectActivityInfo); + loadLibrary("sfml-graphics", lJNIEnv, ObjectActivityInfo); + loadLibrary("ogg", lJNIEnv, ObjectActivityInfo); + loadLibrary("FLAC", lJNIEnv, ObjectActivityInfo); + loadLibrary("vorbis", lJNIEnv, ObjectActivityInfo); + loadLibrary("vorbisenc", lJNIEnv, ObjectActivityInfo); + loadLibrary("sndfile", lJNIEnv, ObjectActivityInfo); + loadLibrary("openal", lJNIEnv, ObjectActivityInfo); + loadLibrary("sfml-audio", lJNIEnv, ObjectActivityInfo); + loadLibrary("sfml-network", lJNIEnv, ObjectActivityInfo); + void* handle = loadLibrary("sfml-example", lJNIEnv, ObjectActivityInfo); + + // todo: should we detach the current thread ? because if we do, it + // crashes (lJavaVM->DetachCurrentThread();) + + // Call the original ANativeActivity_onCreate function + void (*ANativeActivity_onCreate)(ANativeActivity*, void*, size_t) = dlsym(handle, "ANativeActivity_onCreate"); + + if (!ANativeActivity_onCreate) + { + LOGE("sfml-activity: Undefined symbol ANativeActivity_onCreate"); + exit(1); + } + + ANativeActivity_onCreate(activity, savedState, savedStateSize); +}