native: Drop C++17 deprecated codecvt and use JNI instead, add modern ICU lib for future android API 31

This commit is contained in:
WeiguangTWK 2026-03-31 15:38:43 +08:00
parent cbc571cf7a
commit ca3fb1b05a
2 changed files with 133 additions and 6 deletions

View File

@ -68,4 +68,8 @@ target_link_libraries(${CMAKE_PROJECT_NAME}
log log
fmt) 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) target_compile_features(${CMAKE_PROJECT_NAME} PRIVATE cxx_std_23)

View File

@ -1,11 +1,12 @@
#include "Misc.hpp" #include "Misc.hpp"
#include <codecvt>
#include <locale>
#include "fmt/core.h" #include "fmt/core.h"
#ifndef GKMS_WINDOWS #ifndef GKMS_WINDOWS
#include <jni.h> #include <jni.h>
#if defined(__ANDROID__) && __ANDROID_API__ >= 31
#include <unicode/ustring.h>
#endif
extern JavaVM* g_javaVM; extern JavaVM* g_javaVM;
#else #else
@ -32,14 +33,136 @@ namespace GakumasLocal::Misc {
return utility::conversions::utf16_to_utf8(wstr); return utility::conversions::utf16_to_utf8(wstr);
} }
#else #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<jclass>(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<jstring>(env->NewGlobalRef(localUtf8));
env->DeleteLocalRef(localUtf8);
return utf8Charset;
}
}
std::u16string ToUTF16(const std::string_view& str) { std::u16string ToUTF16(const std::string_view& str) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv; #if defined(__ANDROID__) && __ANDROID_API__ >= 31
return utf16conv.from_bytes(str.data(), str.data() + str.size()); UErrorCode status = U_ZERO_ERROR;
int32_t outLen = 0;
u_strFromUTF8(nullptr, 0, &outLen, str.data(), static_cast<int32_t>(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<UChar*>(out.data()),
outLen,
&outLen,
str.data(),
static_cast<int32_t>(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, "<init>", "([BLjava/lang/String;)V");
if (!ctor) return {};
jbyteArray bytes = env->NewByteArray(static_cast<jsize>(str.size()));
if (!bytes) return {};
env->SetByteArrayRegion(bytes, 0, static_cast<jsize>(str.size()), reinterpret_cast<const jbyte*>(str.data()));
jstring jstr = reinterpret_cast<jstring>(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<const char16_t*>(chars), reinterpret_cast<const char16_t*>(chars) + len);
env->ReleaseStringChars(jstr, chars);
env->DeleteLocalRef(jstr);
return out;
#endif
} }
std::string ToUTF8(const std::u16string_view& str) { std::string ToUTF8(const std::u16string_view& str) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv; #if defined(__ANDROID__) && __ANDROID_API__ >= 31
return utf16conv.to_bytes(str.data(), str.data() + str.size()); UErrorCode status = U_ZERO_ERROR;
int32_t outLen = 0;
u_strToUTF8(nullptr, 0, &outLen, reinterpret_cast<const UChar*>(str.data()), static_cast<int32_t>(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<const UChar*>(str.data()),
static_cast<int32_t>(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<const jchar*>(str.data()), static_cast<jsize>(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<jbyteArray>(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<size_t>(len), '\0');
env->GetByteArrayRegion(bytes, 0, len, reinterpret_cast<jbyte*>(out.data()));
env->DeleteLocalRef(bytes);
return out;
#endif
} }
#endif #endif