From ca3fb1b05abc047bc9780e5df31e62fe572577a3 Mon Sep 17 00:00:00 2001 From: WeiguangTWK Date: Tue, 31 Mar 2026 15:38:43 +0800 Subject: [PATCH] native: Drop C++17 deprecated codecvt and use JNI instead, add modern ICU lib for future android API 31 --- app/src/main/cpp/CMakeLists.txt | 4 + app/src/main/cpp/GakumasLocalify/Misc.cpp | 135 +++++++++++++++++++++- 2 files changed, 133 insertions(+), 6 deletions(-) diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index d948cd3..9a4c188 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -68,4 +68,8 @@ target_link_libraries(${CMAKE_PROJECT_NAME} log fmt) +if (ANDROID AND ANDROID_PLATFORM_LEVEL GREATER_EQUAL 31) + target_link_libraries(${CMAKE_PROJECT_NAME} icu) +endif() + target_compile_features(${CMAKE_PROJECT_NAME} PRIVATE cxx_std_23) diff --git a/app/src/main/cpp/GakumasLocalify/Misc.cpp b/app/src/main/cpp/GakumasLocalify/Misc.cpp index 0f63390..295d1b5 100644 --- a/app/src/main/cpp/GakumasLocalify/Misc.cpp +++ b/app/src/main/cpp/GakumasLocalify/Misc.cpp @@ -1,11 +1,12 @@ #include "Misc.hpp" -#include -#include #include "fmt/core.h" #ifndef GKMS_WINDOWS #include + #if defined(__ANDROID__) && __ANDROID_API__ >= 31 + #include + #endif extern JavaVM* g_javaVM; #else @@ -32,14 +33,136 @@ namespace GakumasLocal::Misc { return utility::conversions::utf16_to_utf8(wstr); } #else + namespace { + jclass GetStringClass(JNIEnv* env) { + static jclass stringClass = nullptr; + if (stringClass) return stringClass; + + jclass localClass = env->FindClass("java/lang/String"); + if (!localClass) return nullptr; + stringClass = reinterpret_cast(env->NewGlobalRef(localClass)); + env->DeleteLocalRef(localClass); + return stringClass; + } + + jstring GetUtf8CharsetName(JNIEnv* env) { + static jstring utf8Charset = nullptr; + if (utf8Charset) return utf8Charset; + + jstring localUtf8 = env->NewStringUTF("UTF-8"); + if (!localUtf8) return nullptr; + utf8Charset = reinterpret_cast(env->NewGlobalRef(localUtf8)); + env->DeleteLocalRef(localUtf8); + return utf8Charset; + } + } + std::u16string ToUTF16(const std::string_view& str) { - std::wstring_convert, char16_t> utf16conv; - return utf16conv.from_bytes(str.data(), str.data() + str.size()); +#if defined(__ANDROID__) && __ANDROID_API__ >= 31 + UErrorCode status = U_ZERO_ERROR; + int32_t outLen = 0; + u_strFromUTF8(nullptr, 0, &outLen, str.data(), static_cast(str.size()), &status); + if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status)) return {}; + + status = U_ZERO_ERROR; + std::u16string out(outLen, u'\0'); + u_strFromUTF8( + reinterpret_cast(out.data()), + outLen, + &outLen, + str.data(), + static_cast(str.size()), + &status); + if (U_FAILURE(status)) return {}; + out.resize(outLen); + return out; +#else + JNIEnv* env = GetJNIEnv(); + if (!env) return {}; + + jclass stringClass = GetStringClass(env); + jstring utf8Charset = GetUtf8CharsetName(env); + if (!stringClass || !utf8Charset) return {}; + + jmethodID ctor = env->GetMethodID(stringClass, "", "([BLjava/lang/String;)V"); + if (!ctor) return {}; + + jbyteArray bytes = env->NewByteArray(static_cast(str.size())); + if (!bytes) return {}; + env->SetByteArrayRegion(bytes, 0, static_cast(str.size()), reinterpret_cast(str.data())); + + jstring jstr = reinterpret_cast(env->NewObject(stringClass, ctor, bytes, utf8Charset)); + env->DeleteLocalRef(bytes); + if (!jstr || env->ExceptionCheck()) { + env->ExceptionClear(); + if (jstr) env->DeleteLocalRef(jstr); + return {}; + } + + const jsize len = env->GetStringLength(jstr); + const jchar* chars = env->GetStringChars(jstr, nullptr); + if (!chars) { + env->DeleteLocalRef(jstr); + return {}; + } + + std::u16string out(reinterpret_cast(chars), reinterpret_cast(chars) + len); + env->ReleaseStringChars(jstr, chars); + env->DeleteLocalRef(jstr); + return out; +#endif } std::string ToUTF8(const std::u16string_view& str) { - std::wstring_convert, char16_t> utf16conv; - return utf16conv.to_bytes(str.data(), str.data() + str.size()); +#if defined(__ANDROID__) && __ANDROID_API__ >= 31 + UErrorCode status = U_ZERO_ERROR; + int32_t outLen = 0; + u_strToUTF8(nullptr, 0, &outLen, reinterpret_cast(str.data()), static_cast(str.size()), &status); + if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status)) return {}; + + status = U_ZERO_ERROR; + std::string out(outLen, '\0'); + u_strToUTF8( + out.data(), + outLen, + &outLen, + reinterpret_cast(str.data()), + static_cast(str.size()), + &status); + if (U_FAILURE(status)) return {}; + out.resize(outLen); + return out; +#else + JNIEnv* env = GetJNIEnv(); + if (!env) return {}; + + jclass stringClass = GetStringClass(env); + jstring utf8Charset = GetUtf8CharsetName(env); + if (!stringClass || !utf8Charset) return {}; + + jstring jstr = env->NewString(reinterpret_cast(str.data()), static_cast(str.size())); + if (!jstr) return {}; + + jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); + if (!getBytes) { + env->DeleteLocalRef(jstr); + return {}; + } + + jbyteArray bytes = reinterpret_cast(env->CallObjectMethod(jstr, getBytes, utf8Charset)); + env->DeleteLocalRef(jstr); + if (!bytes || env->ExceptionCheck()) { + env->ExceptionClear(); + if (bytes) env->DeleteLocalRef(bytes); + return {}; + } + + const jsize len = env->GetArrayLength(bytes); + std::string out(static_cast(len), '\0'); + env->GetByteArrayRegion(bytes, 0, len, reinterpret_cast(out.data())); + env->DeleteLocalRef(bytes); + return out; +#endif } #endif