Browse Source

Merge pull request #30292 from neikeq/android_fixes

Mono: Android build and shared libraries fixes
Rémi Verschelde 6 years ago
parent
commit
e1478b7a87

+ 69 - 0
modules/mono/build_scripts/make_android_mono_config.py

@@ -0,0 +1,69 @@
+
+def generate_compressed_config(config_src, output_dir):
+    import os.path
+    from compat import byte_to_str
+
+    # Header file
+    with open(os.path.join(output_dir, 'android_mono_config.gen.h'), 'w') as header:
+        header.write('''/* THIS FILE IS GENERATED DO NOT EDIT */
+#ifndef ANDROID_MONO_CONFIG_GEN_H
+#define ANDROID_MONO_CONFIG_GEN_H
+
+#ifdef ANDROID_ENABLED
+
+#include "core/ustring.h"
+
+String get_godot_android_mono_config();
+
+#endif // ANDROID_ENABLED
+
+#endif // ANDROID_MONO_CONFIG_GEN_H
+''')
+
+    # Source file
+    with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp:
+        with open(config_src, 'rb') as f:
+            buf = f.read()
+            decompr_size = len(buf)
+            import zlib
+            buf = zlib.compress(buf)
+            compr_size = len(buf)
+
+            bytes_seq_str = ''
+            for i, buf_idx in enumerate(range(compr_size)):
+                if i > 0:
+                    bytes_seq_str += ', '
+                bytes_seq_str += byte_to_str(buf[buf_idx])
+
+            cpp.write('''/* THIS FILE IS GENERATED DO NOT EDIT */
+#include "android_mono_config.gen.h"
+
+#ifdef ANDROID_ENABLED
+
+#include "core/io/compression.h"
+#include "core/pool_vector.h"
+
+namespace {
+
+// config
+static const int config_compressed_size = %d;
+static const int config_uncompressed_size = %d;
+static const unsigned char config_compressed_data[] = { %s };
+
+} // namespace
+
+String get_godot_android_mono_config() {
+	PoolVector<uint8_t> data;
+	data.resize(config_uncompressed_size);
+	PoolVector<uint8_t>::Write w = data.write();
+	Compression::decompress(w.ptr(), config_uncompressed_size, config_compressed_data,
+			config_compressed_size, Compression::MODE_DEFLATE);
+	String s;
+	if (s.parse_utf8((const char *)w.ptr(), data.size())) {
+		ERR_FAIL_V(String());
+	}
+	return s;
+}
+
+#endif // ANDROID_ENABLED
+''' % (compr_size, decompr_size, bytes_seq_str))

+ 11 - 10
modules/mono/build_scripts/make_cs_compressed_header.py

@@ -23,30 +23,31 @@ def generate_header(src, dst, version_dst):
                 latest_mtime = mtime if mtime > latest_mtime else latest_mtime
                 with open(filepath, 'rb') as f:
                     buf = f.read()
-                    decomp_size = len(buf)
+                    decompr_size = len(buf)
                     import zlib
                     buf = zlib.compress(buf)
+                    compr_size = len(buf)
                     name = str(cs_file_count)
                     header.write('\n')
                     header.write('// ' + filepath_src_rel + '\n')
-                    header.write('static const int _cs_' + name + '_compressed_size = ' + str(len(buf)) + ';\n')
-                    header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decomp_size) + ';\n')
+                    header.write('static const int _cs_' + name + '_compressed_size = ' + str(compr_size) + ';\n')
+                    header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decompr_size) + ';\n')
                     header.write('static const unsigned char _cs_' + name + '_compressed[] = { ')
-                    for i, buf_idx in enumerate(range(len(buf))):
+                    for i, buf_idx in enumerate(range(compr_size)):
                         if i > 0:
                             header.write(', ')
                         header.write(byte_to_str(buf[buf_idx]))
+                    header.write(' };\n')
                     inserted_files += '\tr_files.insert("' + filepath_src_rel.replace('\\', '\\\\') + '", ' \
-                                        'CompressedFile(_cs_' + name + '_compressed_size, ' \
+                                        'GodotCsCompressedFile(_cs_' + name + '_compressed_size, ' \
                                         '_cs_' + name + '_uncompressed_size, ' \
                                         '_cs_' + name + '_compressed));\n'
-                    header.write(' };\n')
-        header.write('\nstruct CompressedFile\n' '{\n'
+        header.write('\nstruct GodotCsCompressedFile\n' '{\n'
             '\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* data;\n'
-            '\n\tCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n'
+            '\n\tGodotCsCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n'
             '\t{\n' '\t\tcompressed_size = p_comp_size;\n' '\t\tuncompressed_size = p_uncomp_size;\n'
-            '\t\tdata = p_data;\n' '\t}\n' '\n\tCompressedFile() {}\n' '};\n'
-            '\nvoid get_compressed_files(Map<String, CompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n'
+            '\t\tdata = p_data;\n' '\t}\n' '\n\tGodotCsCompressedFile() {}\n' '};\n'
+            '\nvoid get_compressed_files(Map<String, GodotCsCompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n'
             )
         header.write('\n#endif // TOOLS_ENABLED\n')
         header.write('\n#endif // CS_COMPRESSED_H\n')

+ 16 - 5
modules/mono/build_scripts/mono_configure.py

@@ -41,7 +41,7 @@ def copy_file(src_dir, dst_dir, name):
     dst_dir = Dir(dst_dir).abspath
 
     if not os.path.isdir(dst_dir):
-        os.mkdir(dst_dir)
+        os.makedirs(dst_dir)
 
     copy(src_path, dst_dir)
 
@@ -65,6 +65,10 @@ def configure(env, env_mono):
         # TODO: Implement this. We have to add the data directory to the apk, concretely the Api and Tools folders.
         raise RuntimeError('This module does not currently support building for android with tools enabled')
 
+    if is_android and mono_static:
+        # When static linking and doing something that requires libmono-native, we get a dlopen error as libmono-native seems to depend on libmonosgen-2.0
+        raise RuntimeError('Linking Mono statically is not currently supported on Android')
+
     if (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')) and not mono_prefix:
         print("WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead")
 
@@ -188,7 +192,7 @@ def configure(env, env_mono):
             if is_apple:
                 env.Append(LIBS=['iconv', 'pthread'])
             elif is_android:
-                env.Append(LIBS=['m', 'dl'])
+                pass # Nothing
             else:
                 env.Append(LIBS=['m', 'rt', 'dl', 'pthread'])
 
@@ -236,6 +240,14 @@ def configure(env, env_mono):
             mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip()
 
         make_template_dir(env, mono_root)
+    elif not tools_enabled and is_android:
+        # Compress Android Mono Config
+        from . import make_android_mono_config
+        config_file_path = os.path.join(mono_root, 'etc', 'mono', 'config')
+        make_android_mono_config.generate_compressed_config(config_file_path, 'mono_gd/')
+
+        # Copy the required shared libraries
+        copy_mono_shared_libs(env, mono_root, None)
 
     if copy_mono_root:
         if not mono_root:
@@ -270,9 +282,8 @@ def make_template_dir(env, mono_root):
 
     # Copy etc/mono/
 
-    if platform != 'android':
-        template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono')
-        copy_mono_etc_dir(mono_root, template_mono_config_dir, env['platform'])
+    template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono')
+    copy_mono_etc_dir(mono_root, template_mono_config_dir, env['platform'])
 
     # Copy the required shared libraries
 

+ 7 - 6
modules/mono/csharp_script.cpp

@@ -682,19 +682,20 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
 
 	GDMonoAssembly *proj_assembly = gdmono->get_project_assembly();
 
-	String name = ProjectSettings::get_singleton()->get("application/config/name");
-	if (name.empty()) {
-		name = "UnnamedProject";
+	String appname = ProjectSettings::get_singleton()->get("application/config/name");
+	String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+	if (appname_safe.empty()) {
+		appname_safe = "UnnamedProject";
 	}
 
-	name += ".dll";
+	appname_safe += ".dll";
 
 	if (proj_assembly) {
 		String proj_asm_path = proj_assembly->get_path();
 
 		if (!FileAccess::exists(proj_assembly->get_path())) {
 			// Maybe it wasn't loaded from the default path, so check this as well
-			proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name);
+			proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe);
 			if (!FileAccess::exists(proj_asm_path))
 				return false; // No assembly to load
 		}
@@ -702,7 +703,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
 		if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time())
 			return false; // Already up to date
 	} else {
-		if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name)))
+		if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe)))
 			return false; // No assembly to load
 	}
 

+ 3 - 3
modules/mono/editor/bindings_generator.cpp

@@ -914,12 +914,12 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
 
 	// Generate sources from compressed files
 
-	Map<String, CompressedFile> compressed_files;
+	Map<String, GodotCsCompressedFile> compressed_files;
 	get_compressed_files(compressed_files);
 
-	for (Map<String, CompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) {
+	for (Map<String, GodotCsCompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) {
 		const String &file_name = E->key();
-		const CompressedFile &file_data = E->value();
+		const GodotCsCompressedFile &file_data = E->value();
 
 		String output_file = path_join(core_dir, file_name);
 

+ 8 - 7
modules/mono/editor/godotsharp_editor.cpp

@@ -63,16 +63,17 @@ bool GodotSharpEditor::_create_project_solution() {
 	pr.step(TTR("Generating C# project..."));
 
 	String path = OS::get_singleton()->get_resource_dir();
-	String name = ProjectSettings::get_singleton()->get("application/config/name");
-	if (name.empty()) {
-		name = "UnnamedProject";
+	String appname = ProjectSettings::get_singleton()->get("application/config/name");
+	String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+	if (appname_safe.empty()) {
+		appname_safe = "UnnamedProject";
 	}
 
-	String guid = CSharpProject::generate_game_project(path, name);
+	String guid = CSharpProject::generate_game_project(path, appname_safe);
 
 	if (guid.length()) {
 
-		DotNetSolution solution(name);
+		DotNetSolution solution(appname_safe);
 
 		if (!solution.set_path(path)) {
 			show_error_dialog(TTR("Failed to create solution."));
@@ -81,12 +82,12 @@ bool GodotSharpEditor::_create_project_solution() {
 
 		DotNetSolution::ProjectInfo proj_info;
 		proj_info.guid = guid;
-		proj_info.relpath = name + ".csproj";
+		proj_info.relpath = appname_safe + ".csproj";
 		proj_info.configs.push_back("Debug");
 		proj_info.configs.push_back("Release");
 		proj_info.configs.push_back("Tools");
 
-		solution.add_new_project(name, proj_info);
+		solution.add_new_project(appname_safe, proj_info);
 
 		Error sln_error = solution.save();
 

+ 7 - 6
modules/mono/editor/godotsharp_export.cpp

@@ -106,13 +106,14 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
 		Map<String, String> dependencies;
 
 		String project_dll_name = ProjectSettings::get_singleton()->get("application/config/name");
-		if (project_dll_name.empty()) {
-			project_dll_name = "UnnamedProject";
+		String project_dll_name_safe = OS::get_singleton()->get_safe_dir_name(project_dll_name);
+		if (project_dll_name_safe.empty()) {
+			project_dll_name_safe = "UnnamedProject";
 		}
 
 		String project_dll_src_dir = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config);
-		String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name + ".dll");
-		dependencies.insert(project_dll_name, project_dll_src_path);
+		String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name_safe + ".dll");
+		dependencies.insert(project_dll_name_safe, project_dll_src_path);
 
 		{
 			MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
@@ -122,10 +123,10 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
 			_GDMONO_SCOPE_DOMAIN_(export_domain);
 
 			GDMonoAssembly *scripts_assembly = NULL;
-			bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name,
+			bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name_safe,
 					project_dll_src_path, &scripts_assembly, /* refonly: */ true);
 
-			ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name);
+			ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name_safe);
 			ERR_FAIL_COND(!load_success);
 
 			Vector<String> search_dirs;

+ 23 - 7
modules/mono/godotsharp_dirs.cpp

@@ -39,6 +39,10 @@
 #include "editor/editor_settings.h"
 #endif
 
+#ifdef __ANDROID__
+#include "utils/android_utils.h"
+#endif
+
 namespace GodotSharpDirs {
 
 String _get_expected_build_config() {
@@ -129,15 +133,16 @@ private:
 		mono_solutions_dir = mono_user_dir.plus_file("solutions");
 		build_logs_dir = mono_user_dir.plus_file("build_logs");
 
-		String name = ProjectSettings::get_singleton()->get("application/config/name");
-		if (name.empty()) {
-			name = "UnnamedProject";
+		String appname = ProjectSettings::get_singleton()->get("application/config/name");
+		String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+		if (appname_safe.empty()) {
+			appname_safe = "UnnamedProject";
 		}
 
 		String base_path = ProjectSettings::get_singleton()->globalize_path("res://");
 
-		sln_filepath = base_path.plus_file(name + ".sln");
-		csproj_filepath = base_path.plus_file(name + ".csproj");
+		sln_filepath = base_path.plus_file(appname_safe + ".sln");
+		csproj_filepath = base_path.plus_file(appname_safe + ".csproj");
 #endif
 
 		String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir();
@@ -150,7 +155,12 @@ private:
 
 		String data_mono_root_dir = data_dir_root.plus_file("Mono");
 		data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
+
+#if __ANDROID__
+		data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir();
+#else
 		data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
+#endif
 
 #ifdef WINDOWS_ENABLED
 		data_mono_bin_dir = data_mono_root_dir.plus_file("bin");
@@ -173,15 +183,21 @@ private:
 
 #else
 
-		String appname = OS::get_singleton()->get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/name"));
-		String data_dir_root = exe_dir.plus_file("data_" + appname);
+		String appname = ProjectSettings::get_singleton()->get("application/config/name");
+		String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+		String data_dir_root = exe_dir.plus_file("data_" + appname_safe);
 		if (!DirAccess::exists(data_dir_root)) {
 			data_dir_root = exe_dir.plus_file("data_Godot");
 		}
 
 		String data_mono_root_dir = data_dir_root.plus_file("Mono");
 		data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
+
+#if __ANDROID__
+		data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir();
+#else
 		data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
+#endif
 
 #ifdef WINDOWS_ENABLED
 		data_mono_bin_dir = data_mono_root_dir.plus_file("bin");

+ 13 - 4
modules/mono/mono_gd/gd_mono.cpp

@@ -56,6 +56,10 @@
 #include "main/main.h"
 #endif
 
+#ifdef ANDROID_ENABLED
+#include "android_mono_config.gen.h"
+#endif
+
 #define OUT_OF_SYNC_ERR_MESSAGE(m_assembly_name) "The assembly '" m_assembly_name "' is out of sync. "                    \
 												 "This error is expected if you just upgraded to a newer Godot version. " \
 												 "Building the project will update the assembly to the correct version."
@@ -287,7 +291,11 @@ void GDMono::initialize() {
 	gdmono_debug_init();
 #endif
 
+#ifdef ANDROID_ENABLED
+	mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data());
+#else
 	mono_config_parse(NULL);
+#endif
 
 	mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL);
 
@@ -651,12 +659,13 @@ bool GDMono::_load_project_assembly() {
 	if (project_assembly)
 		return true;
 
-	String name = ProjectSettings::get_singleton()->get("application/config/name");
-	if (name.empty()) {
-		name = "UnnamedProject";
+	String appname = ProjectSettings::get_singleton()->get("application/config/name");
+	String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+	if (appname_safe.empty()) {
+		appname_safe = "UnnamedProject";
 	}
 
-	bool success = load_assembly(name, &project_assembly);
+	bool success = load_assembly(appname_safe, &project_assembly);
 
 	if (success) {
 		mono_assembly_set_main(project_assembly->get_assembly());

+ 12 - 1
modules/mono/mono_gd/gd_mono_assembly.cpp

@@ -270,7 +270,18 @@ Error GDMonoAssembly::load(bool p_refonly) {
 	Vector<uint8_t> data = FileAccess::get_file_as_array(path);
 	ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ);
 
-	String image_filename = ProjectSettings::get_singleton()->globalize_path(path);
+	String image_filename;
+
+#ifdef ANDROID_ENABLED
+	if (path.begins_with("res://")) {
+		image_filename = path.substr(6, path.length());
+	} else {
+		image_filename = ProjectSettings::get_singleton()->globalize_path(path);
+	}
+#else
+	// FIXME: globalize_path does not work on exported games
+	image_filename = ProjectSettings::get_singleton()->globalize_path(path);
+#endif
 
 	MonoImageOpenStatus status = MONO_IMAGE_OK;
 

+ 68 - 0
modules/mono/utils/android_utils.cpp

@@ -0,0 +1,68 @@
+/*************************************************************************/
+/*  android_utils.cpp                                                    */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "android_utils.h"
+
+#ifdef __ANDROID__
+
+#include "platform/android/thread_jandroid.h"
+
+namespace GDMonoUtils {
+namespace Android {
+
+String get_app_native_lib_dir() {
+	JNIEnv *env = ThreadAndroid::get_env();
+
+	jclass activityThreadClass = env->FindClass("android/app/ActivityThread");
+	jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;");
+	jobject activityThread = env->CallStaticObjectMethod(activityThreadClass, currentActivityThread);
+	jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;");
+	jobject ctx = env->CallObjectMethod(activityThread, getApplication);
+
+	jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
+	jobject applicationInfo = env->CallObjectMethod(ctx, getApplicationInfo);
+	jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;");
+	jstring nativeLibraryDir = (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField);
+
+	String result;
+
+	const char *const nativeLibraryDir_utf8 = env->GetStringUTFChars(nativeLibraryDir, NULL);
+	if (nativeLibraryDir_utf8) {
+		result.parse_utf8(nativeLibraryDir_utf8);
+		env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDir_utf8);
+	}
+
+	return result;
+}
+
+} // namespace Android
+} // namespace GDMonoUtils
+
+#endif // __ANDROID__

+ 48 - 0
modules/mono/utils/android_utils.h

@@ -0,0 +1,48 @@
+/*************************************************************************/
+/*  android_utils.h                                                      */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef ANDROID_UTILS_H
+#define ANDROID_UTILS_H
+
+#ifdef __ANDROID__
+
+#include "core/ustring.h"
+
+namespace GDMonoUtils {
+namespace Android {
+
+String get_app_native_lib_dir();
+
+} // namespace Android
+} // namespace GDMonoUtils
+
+#endif // __ANDROID__
+
+#endif // ANDROID_UTILS_H