diff --git a/arkoala/framework/native/meson.build b/arkoala/framework/native/meson.build index ee0fdac7c3df13a9f34cbb84ca171fc7803f076a..06f583c8945f93f738f7789a141878a8c3faf1a4 100644 --- a/arkoala/framework/native/meson.build +++ b/arkoala/framework/native/meson.build @@ -346,10 +346,10 @@ if get_option('vmloader') == true if get_option('vmloader_apis').contains('ets') include_dirs += [ - interop_src / 'ets', + interop_src / 'ani', ] vmloader_cflags += [ - '-DKOALA_ETS_NAPI', + '-DKOALA_ANI', ] endif diff --git a/interop/src/cpp/vmloader.cc b/interop/src/cpp/vmloader.cc index 3a4d41f02dd7ddac198b3e88c0a9e1667de52566..8aa29e691525df4584039cb661140b47705a19b4 100644 --- a/interop/src/cpp/vmloader.cc +++ b/interop/src/cpp/vmloader.cc @@ -13,9 +13,10 @@ * limitations under the License. */ +#include #include +#include #include -#include #include "interop-logging.h" #include "dynamic-loader.h" @@ -87,7 +88,7 @@ const VMLibInfo javaVMLib = { }; #endif -#ifdef KOALA_ETS_NAPI +#if defined(KOALA_ETS_NAPI) || defined(KOALA_ANI) const VMLibInfo pandaVMLib = { // sdkPath #if defined(KOALA_OHOS) @@ -128,7 +129,13 @@ const VMLibInfo pandaVMLib = { , // createVM - "ETS_CreateVM" + #if defined(KOALA_ETS_NAPI) + "ETS_CreateVM" + #elif defined(KOALA_ANI) + "ANI_CreateVM" + #else + #error "Unknown Native API type" + #endif }; #endif @@ -160,8 +167,13 @@ struct VMEntry { VMEntry g_vmEntry = {}; +#ifdef KOALA_ANI +typedef int (*createVM_t)(const void* args, uint32_t version, void** pVM); +typedef int (*getVMs_t)(void** pVM, size_t bufLen, size_t* nVMs); +#else typedef int (*createVM_t)(void** pVM, void** pEnv, void* vmInitArgs); typedef int (*getVMs_t)(void** pVM, int32_t bufLen, int32_t* nVMs); +#endif #ifdef KOALA_WINDOWS #define DLL_EXPORT __declspec(dllexport) @@ -209,6 +221,47 @@ int ArkMobileLog(int id, int level, const char *component, const char *fmt, cons return 0; } +std::pair GetBootAndAppPandaFiles(const VMLibInfo* thisVM, const char* appClassPath) +{ + std::stringstream bootPandaFilesStream; +#if USE_SYSTEM_ARKVM + bootPandaFilesStream << SYSTEM_ARK_STDLIB_PATH; +#elif defined(KOALA_OHOS) + bootPandaFilesStream << OHOS_USER_LIBS << "/etsstdlib.abc"; +#elif defined(KOALA_LINUX) || defined(KOALA_MACOS) || defined(KOALA_WINDOWS) + bootPandaFilesStream << thisVM->sdkPath << "/ets/etsstdlib.abc"; +#endif + + std::vector files; + traverseDir(std::string(appClassPath), files); + std::sort(files.begin(), files.end()); + + std::stringstream appFilesStream; + for (size_t idx = 0, end = files.size(); idx < end; ++idx) { + if (idx > 0) { + appFilesStream << ':'; + } + appFilesStream << files[idx]; + } + auto appFiles = appFilesStream.str(); + + return {bootPandaFilesStream.str(), appFilesStream.str()}; +} + +} +#endif + +#if defined(KOALA_ANI) +static bool ResetErrorIfExists(ani_env *env) +{ + ani_boolean hasError = ANI_FALSE; + env->ExistUnhandledError(&hasError); + if (hasError == ANI_TRUE) { + env->DescribeError(); + env->ResetError(); + return true; + } + return false; } #endif @@ -251,7 +304,12 @@ extern "C" DLL_EXPORT KInt LoadVirtualMachine(KInt vmKind, const char* appClassP } createVM_t createVM = (createVM_t)findSymbol(handle, thisVM->createVM); - getVMs_t getVMs = (getVMs_t)findSymbol(handle, "ETS_GetCreatedVMs"); + getVMs_t getVMs = nullptr; +#if defined(KOALA_ETS_NAPI) + getVMs = (getVMs_t)findSymbol(handle, "ETS_GetCreatedVMs"); +#elif defined(KOALA_ANI) + getVMs = (getVMs_t)findSymbol(handle, "ANI_GetCreatedVMs"); +#endif if (!createVM) { LOGE("Cannot find %" LOG_PUBLIC "s\n", thisVM->createVM); @@ -260,7 +318,6 @@ extern "C" DLL_EXPORT KInt LoadVirtualMachine(KInt vmKind, const char* appClassP void* vm = nullptr; void* env = nullptr; - int32_t nVMs = 0; int result = 0; #ifdef KOALA_JNI @@ -280,9 +337,50 @@ extern "C" DLL_EXPORT KInt LoadVirtualMachine(KInt vmKind, const char* appClassP } #endif +#if defined(KOALA_ANI) + if (vmKind == PANDA_ANI_VM_KIND) { + g_vmEntry.vmKind = vmKind; + + uint32_t version = ANI_VERSION_1; + size_t nVMs = 0; + result = getVMs ? getVMs(&vm, 1, &nVMs) : 0; + if (nVMs == 0 && result == 0) { + std::vector pandaVMOptions; + + auto [bootFiles, appFiles] = GetBootAndAppPandaFiles(thisVM, appClassPath); + LOGE("classpath \"%s\" from %s", appFiles.c_str(), appClassPath); + std::string delimiter = ""; + if (!bootFiles.empty() && !appFiles.empty()) { + delimiter = ":"; + } + std::string bootPandaFiles = "--ext:--boot-panda-files=" + bootFiles + delimiter + appFiles; + LOGE("ANI boot-panda-files option: \"%s\"", bootPandaFiles.c_str()); + pandaVMOptions.push_back({bootPandaFiles.c_str(), nullptr}); + + // pandaVMOptions.push_back({"--ext:--gc-trigger-type=heap-trigger", nullptr}); + std::string nativeLibraryPathOption = std::string("--ext:--native-library-path=") + appLibPath; + pandaVMOptions.push_back({nativeLibraryPathOption.c_str(), nullptr}); + // pandaVMOptions.push_back({"--ext:--verification-mode=on-the-fly", nullptr}); + // pandaVMOptions.push_back({"--ext:--compiler-enable-jit=false", nullptr}); + // // TODO(dslynko, #23689): add mobile logger after it's supported by ANI + // pandaVMOptions.push_back({"--ext:--enable-an=true", nullptr}); + ani_options optionsPtr = {pandaVMOptions.size(), pandaVMOptions.data()}; + + result = createVM(&optionsPtr, version, &vm); + } + + if (result == 0) { + ani_vm* vmInstance = (ani_vm*)vm; + ani_env* pEnv = nullptr; + result = vmInstance->GetEnv(version, &pEnv); + env = static_cast(pEnv); + } + } +#endif /* KOALA_ANI */ + // For now we use ETS API for VM startup and entry. -#if defined(KOALA_ETS_NAPI) || defined(KOALA_ANI) - if (vmKind == PANDA_VM_KIND || vmKind == PANDA_ANI_VM_KIND) { +#if defined(KOALA_ETS_NAPI) + if (vmKind == PANDA_VM_KIND) { EtsVMInitArgs pandaVMArgs; pandaVMArgs.version = ETS_NAPI_VERSION_1_0; std::vector etsVMOptions; @@ -317,6 +415,7 @@ extern "C" DLL_EXPORT KInt LoadVirtualMachine(KInt vmKind, const char* appClassP pandaVMArgs.options = etsVMOptions.data(); g_vmEntry.vmKind = vmKind; + size_t nVMs = 0; result = getVMs ? getVMs(&vm, 1, &nVMs) : 0; if (nVMs != 0) { __EtsVM* vmInstance = (__EtsVM*)vm; @@ -369,7 +468,11 @@ const AppInfo javaAppInfo = { #ifdef KOALA_USE_PANDA_VM const AppInfo pandaAppInfo = { +#ifdef KOALA_ANI + "L@koalaui/arkts-arkui/Application/Application;", +#else "@koalaui/arkts-arkui/Application/Application", +#endif "createApplication", "Lstd/core/String;Lstd/core/String;ZI:L@koalaui/arkts-arkui/Application/Application;", "start", @@ -380,7 +483,11 @@ const AppInfo pandaAppInfo = { "IIII:Lstd/core/String;", }; const AppInfo harnessAppInfo = { +#ifdef KOALA_ANI + "L@koalaui/ets-harness/src/EtsHarnessApplication/EtsHarnessApplication;", +#else "@koalaui/ets-harness/src/EtsHarnessApplication/EtsHarnessApplication", +#endif "createApplication", "Lstd/core/String;Lstd/core/String;ZI:L@koalaui/ets-harness/src/EtsHarnessApplication/EtsHarnessApplication;", "start", @@ -445,8 +552,8 @@ extern "C" DLL_EXPORT KNativePointer StartApplication(const char* appUrl, const app, start)); } #endif -#if defined(KOALA_ETS_NAPI) || defined(KOALA_ANI) - if (g_vmEntry.vmKind == PANDA_VM_KIND || g_vmEntry.vmKind == PANDA_ANI_VM_KIND) { +#if defined(KOALA_ETS_NAPI) + if (g_vmEntry.vmKind == PANDA_VM_KIND) { EtsEnv* etsEnv = (EtsEnv*)g_vmEntry.env; ets_class appClass = etsEnv->FindClass(appInfo->className); if (!appClass) { @@ -525,6 +632,105 @@ extern "C" DLL_EXPORT KNativePointer StartApplication(const char* appUrl, const // TODO: pass app entry point! return reinterpret_cast(etsEnv->CallLongMethod((ets_object)(app), start, &g_vmEntry.foreignVMContext)); } +#endif +#if defined(KOALA_ANI) + if (g_vmEntry.vmKind == PANDA_ANI_VM_KIND) { + auto *env = reinterpret_cast(g_vmEntry.env); + + ani_class appClass {}; + auto status = env->FindClass(appInfo->className, &appClass); + if (status != ANI_OK) { + LOGE("Cannot load main class %" LOG_PUBLIC "s\n", appInfo->className); + ResetErrorIfExists(env); + return nullptr; + } + ani_static_method create {}; + status = env->Class_FindStaticMethod(appClass, appInfo->createMethodName, appInfo->createMethodSig, &create); + if (status != ANI_OK) { + LOGE("Cannot find create method %" LOG_PUBLIC "s\n", appInfo->createMethodName); + ResetErrorIfExists(env); + return nullptr; + } + +#if defined (KOALA_OHOS_ARM64) + ani_boolean useNativeLog = ANI_TRUE; +#else + ani_boolean useNativeLog = ANI_FALSE; +#endif + ani_string appUrlString {}; + status = env->String_NewUTF8(appUrl, strlen(appUrl), &appUrlString); + if (status != ANI_OK) { + ResetErrorIfExists(env); + return nullptr; + } + ani_string appParamsString {}; + status = env->String_NewUTF8(appParams, strlen(appParams), &appParamsString); + if (status != ANI_OK) { + ResetErrorIfExists(env); + return nullptr; + } + + ani_ref appInstance {}; + status = env->Class_CallStaticMethod_Ref(appClass, create, &appInstance, appUrlString, appParamsString, + useNativeLog, static_cast(g_vmEntry.vmKind)); + if (status != ANI_OK) { + LOGE("createApplication returned null"); + ResetErrorIfExists(env); + return nullptr; + } + ani_ref app {}; + status = env->GlobalReference_Create(appInstance, &app); + if (status != ANI_OK) { + ResetErrorIfExists(env); + return nullptr; + } + g_vmEntry.app = (void*)app; + + ani_method start {}; + status = env->Class_FindMethod(appClass, appInfo->startMethodName, appInfo->startMethodSig, &start); + if (status != ANI_OK) { + ResetErrorIfExists(env); + return nullptr; + } + ani_method enter {}; + status = env->Class_FindMethod(appClass, appInfo->enterMethodName, nullptr, &enter); + if (status != ANI_OK) { + LOGE("Cannot find `enter` method %" LOG_PUBLIC "s", appInfo->enterMethodName); + ResetErrorIfExists(env); + return nullptr; + } + g_vmEntry.enter = reinterpret_cast(enter); + ani_method emitEvent {}; + status = env->Class_FindMethod(appClass, appInfo->emitEventMethodName, appInfo->emitEventMethodSig, &emitEvent); + if (status != ANI_OK) { + LOGE("Cannot find `emitEvent` method %" LOG_PUBLIC "s", appInfo->emitEventMethodSig); + ResetErrorIfExists(env); + return nullptr; + } + g_vmEntry.emitEvent = reinterpret_cast(emitEvent); + + if (isTestEnv) { + ani_method restartWith {}; + status = env->Class_FindMethod(appClass, appInfo->restartWithMethodName, appInfo->restartWithMethodSig, &restartWith); + if (status != ANI_OK) { + LOGE("Cannot find `restartWith` method %" LOG_PUBLIC "s", appInfo->restartWithMethodSig); + ResetErrorIfExists(env); + return nullptr; + } + g_vmEntry.restartWith = reinterpret_cast(restartWith); + } + + ani_long ptr = 0; + // TODO: pass app entry point! + status = env->Object_CallMethod_Long(static_cast(appInstance), start, &ptr, + reinterpret_cast(&g_vmEntry.foreignVMContext)); + if (status != ANI_OK) { + LOGE("Cannot start application"); + ResetErrorIfExists(env); + return nullptr; + } + return reinterpret_cast(ptr); + } #endif return nullptr; } @@ -547,8 +753,8 @@ extern "C" DLL_EXPORT KBoolean RunApplication(const KInt arg0, const KInt arg1) return result; } #endif -#if defined(KOALA_ETS_NAPI) || defined(KOALA_ANI) - if (g_vmEntry.vmKind == PANDA_VM_KIND || g_vmEntry.vmKind == PANDA_ANI_VM_KIND) { +#if defined(KOALA_ETS_NAPI) + if (g_vmEntry.vmKind == PANDA_VM_KIND) { EtsEnv* etsEnv = (EtsEnv*)(g_vmEntry.env); if (!g_vmEntry.enter) { LOGE("Cannot find enter method"); @@ -569,6 +775,25 @@ extern "C" DLL_EXPORT KBoolean RunApplication(const KInt arg0, const KInt arg1) return result; } #endif +#if defined(KOALA_ANI) + if (g_vmEntry.vmKind == PANDA_ANI_VM_KIND) { + ani_env* env = reinterpret_cast(g_vmEntry.env); + if (g_vmEntry.enter == nullptr) { + LOGE("Cannot find enter method"); + return -1; + } + ani_boolean result = ANI_FALSE; + auto status = env->Object_CallMethod_Boolean(reinterpret_cast(g_vmEntry.app), + reinterpret_cast(g_vmEntry.enter), &result, + static_cast(arg0), static_cast(arg1), + reinterpret_cast(&g_vmEntry.foreignVMContext)); + if (status != ANI_OK) { + ResetErrorIfExists(env); + return ANI_FALSE; + } + return result; + } +#endif return 1; } @@ -598,8 +823,8 @@ extern "C" DLL_EXPORT const char* EmitEvent(const KInt type, const KInt target, } #endif -#if defined(KOALA_ETS_NAPI) || defined(KOALA_ANI) - if (g_vmEntry.vmKind == PANDA_VM_KIND || g_vmEntry.vmKind == PANDA_ANI_VM_KIND) { +#if defined(KOALA_ETS_NAPI) + if (g_vmEntry.vmKind == PANDA_ANI_VM_KIND) { EtsEnv* etsEnv = (EtsEnv*)(g_vmEntry.env); if (!g_vmEntry.emitEvent) { LOGE("Cannot find emitEvent method"); @@ -621,6 +846,45 @@ extern "C" DLL_EXPORT const char* EmitEvent(const KInt type, const KInt target, const char *result = etsEnv->GetStringUTFChars(rv, 0); return result; } +#endif +#if defined(KOALA_ANI) + if (g_vmEntry.vmKind == PANDA_ANI_VM_KIND) { + ani_env *env = reinterpret_cast(g_vmEntry.env); + if (g_vmEntry.emitEvent == nullptr) { + LOGE("Cannot find emitEvent method"); + return "-1"; + } + ani_ref result {}; + auto status = env->Object_CallMethod_Ref(reinterpret_cast(g_vmEntry.app), + reinterpret_cast(g_vmEntry.emitEvent), + &result, + static_cast(type), + static_cast(target), + static_cast(arg0), + static_cast(arg1)); + if (status != ANI_OK) { + LOGE("Calling emitEvent() method gave an error"); + ResetErrorIfExists(env); + return "-1"; + } + + auto str = static_cast(result); + ani_size sz = 0; + status = env->String_GetUTF8Size(str, &sz); + if (status != ANI_OK) { + ResetErrorIfExists(env); + return "-1"; + } + auto buffer = new char[sz + 1]; + ani_size writtenChars = 0; + status = env->String_GetUTF8(str, buffer, sz + 1, &writtenChars); + if (status != ANI_OK || writtenChars != sz) { + delete [] buffer; + ResetErrorIfExists(env); + return "-1"; + } + return buffer; + } #endif return "-1"; } @@ -644,7 +908,7 @@ extern "C" DLL_EXPORT void RestartWith(const char* page) { } } #endif -#if defined(KOALA_ETS_NAPI) || defined(KOALA_ANI) +#if defined(KOALA_ETS_NAPI) if (g_vmEntry.vmKind == PANDA_VM_KIND) { EtsEnv* etsEnv = (EtsEnv*)(g_vmEntry.env); if (!g_vmEntry.restartWith) { @@ -663,6 +927,27 @@ extern "C" DLL_EXPORT void RestartWith(const char* page) { } } #endif +#if defined(KOALA_ANI) + if (g_vmEntry.vmKind == PANDA_ANI_VM_KIND) { + ani_env *env = reinterpret_cast(g_vmEntry.env); + if (g_vmEntry.restartWith != nullptr) { + LOGE("Cannot find restartWith method"); + return; + } + ani_string pageString {}; + auto status = env->String_NewUTF8(page, strlen(page), &pageString); + if (status != ANI_OK) { + ResetErrorIfExists(env); + return; + } + status = env->Object_CallMethod_Void(reinterpret_cast(g_vmEntry.app), + reinterpret_cast(g_vmEntry.restartWith), pageString); + if (status != ANI_OK) { + LOGE("Calling restartWith() method gave an error"); + ResetErrorIfExists(env); + } + } +#endif } void traverseDir(std::string root, std::vector& paths, int depth) {