-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[Android] Decouple runtime initialization and entry point execution for Android sample #111742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c4e4b64
7d9e6c1
b154867
1601980
9b48461
bea0495
a6a5832
d2f611e
cb399e4
f857e5b
52f8e40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,12 +24,21 @@ void | |
Java_net_dot_MonoRunner_setEnv (JNIEnv* env, jobject thiz, jstring j_key, jstring j_value); | ||
|
||
int | ||
Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_dir, jstring j_cache_dir, jstring j_testresults_dir, jstring j_entryPointLibName, jobjectArray j_args, long current_local_time); | ||
Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_dir, jstring j_entryPointLibName, long current_local_time); | ||
|
||
int | ||
Java_net_dot_MonoRunner_execEntryPoint (JNIEnv* env, jobject thiz, jstring j_entryPointLibName, jobjectArray j_args); | ||
|
||
void | ||
Java_net_dot_MonoRunner_freeNativeResources (JNIEnv* env, jobject thiz); | ||
matouskozak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/********* implementation *********/ | ||
|
||
static char *bundle_path; | ||
static char *executable; | ||
static const char* g_bundle_path = NULL; | ||
static const char* g_executable_path = NULL; | ||
static unsigned int g_coreclr_domainId = 0; | ||
static void* g_coreclr_handle = NULL; | ||
|
||
|
||
#define LOG_INFO(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, "DOTNET", fmt, ##__VA_ARGS__) | ||
#define LOG_ERROR(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, "DOTNET", fmt, ##__VA_ARGS__) | ||
|
@@ -54,21 +63,11 @@ strncpy_str (JNIEnv *env, char *buff, jstring str, int nbuff) | |
jboolean isCopy = 0; | ||
const char *copy_buff = (*env)->GetStringUTFChars (env, str, &isCopy); | ||
strncpy (buff, copy_buff, nbuff); | ||
buff[nbuff - 1] = '\0'; // ensure '\0' terminated | ||
matouskozak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (isCopy) | ||
(*env)->ReleaseStringUTFChars (env, str, copy_buff); | ||
} | ||
|
||
void | ||
Java_net_dot_MonoRunner_setEnv (JNIEnv* env, jobject thiz, jstring j_key, jstring j_value) | ||
{ | ||
LOG_INFO ("Java_net_dot_MonoRunner_setEnv:"); | ||
const char *key = (*env)->GetStringUTFChars(env, j_key, 0); | ||
const char *val = (*env)->GetStringUTFChars(env, j_value, 0); | ||
setenv (key, val, true); | ||
(*env)->ReleaseStringUTFChars(env, j_key, key); | ||
(*env)->ReleaseStringUTFChars(env, j_value, val); | ||
} | ||
|
||
/* | ||
* Get the list of trusted assemblies from a specified @dir_path. | ||
* The path is searched for .dll files which when found are concatenated | ||
|
@@ -130,11 +129,67 @@ get_tpas_from_path(const char* dir_path, const char** tpas) | |
} | ||
|
||
static int | ||
mono_droid_runtime_init (const char* executable, int managed_argc, char* managed_argv[], int local_date_time_offset) | ||
bundle_executable_path (const char* executable, const char* bundle_path, const char** executable_path) | ||
{ | ||
LOG_INFO ("mono_droid_runtime_init called with executable: %s", executable); | ||
size_t executable_path_len = strlen(bundle_path) + strlen(executable) + 1; // +1 for '/' | ||
char* temp_path = (char*)malloc(sizeof(char) * (executable_path_len + 1)); // +1 for '\0' | ||
if (temp_path == NULL) | ||
{ | ||
return -1; | ||
} | ||
|
||
chdir (bundle_path); | ||
size_t res = snprintf(temp_path, (executable_path_len + 1), "%s/%s", bundle_path, executable); | ||
if (res < 0 || res != executable_path_len) | ||
{ | ||
return -1; | ||
} | ||
*executable_path = temp_path; | ||
return executable_path_len; | ||
} | ||
|
||
static void | ||
free_resources () | ||
{ | ||
if (g_bundle_path) | ||
{ | ||
free (g_bundle_path); | ||
g_bundle_path = NULL; | ||
} | ||
if (g_executable_path) | ||
{ | ||
free (g_executable_path); | ||
g_executable_path = NULL; | ||
} | ||
if (g_coreclr_handle) | ||
{ | ||
// Clean up some coreclr resources. This doesn't make coreclr unloadable. | ||
coreclr_shutdown (g_coreclr_handle, g_coreclr_domainId); | ||
g_coreclr_handle = NULL; | ||
} | ||
} | ||
|
||
static int | ||
mono_droid_execute_assembly (const char* executable_path, void* coreclr_handle, unsigned int coreclr_domainId, int managed_argc, const char** managed_argv) | ||
{ | ||
unsigned int rv; | ||
LOG_INFO ("Calling coreclr_execute_assembly"); | ||
coreclr_execute_assembly (coreclr_handle, coreclr_domainId, managed_argc, managed_argv, executable_path, &rv); | ||
LOG_INFO ("Exit code: %u.", rv); | ||
return rv; | ||
} | ||
|
||
static int | ||
mono_droid_runtime_init (const char* executable) | ||
{ | ||
LOG_INFO ("mono_droid_runtime_init (CoreCLR) called with executable: %s", executable); | ||
matouskozak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (bundle_executable_path(executable, g_bundle_path, &g_executable_path) < 0) | ||
{ | ||
LOG_ERROR("Failed to resolve full path for: %s", executable); | ||
return -1; | ||
} | ||
|
||
chdir (g_bundle_path); | ||
|
||
// TODO: set TRUSTED_PLATFORM_ASSEMBLIES, APP_PATHS and NATIVE_DLL_SEARCH_DIRECTORIES | ||
|
||
|
@@ -145,78 +200,99 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed | |
|
||
const char* appctx_values[3]; | ||
appctx_values[0] = ANDROID_RUNTIME_IDENTIFIER; | ||
appctx_values[1] = bundle_path; | ||
size_t tpas_len = get_tpas_from_path(bundle_path, &appctx_values[2]); | ||
appctx_values[1] = g_bundle_path; | ||
size_t tpas_len = get_tpas_from_path(g_bundle_path, &appctx_values[2]); | ||
if (tpas_len < 1) | ||
{ | ||
LOG_ERROR("Failed to get trusted assemblies from path: %s", bundle_path); | ||
LOG_ERROR("Failed to get trusted assemblies from path: %s", g_bundle_path); | ||
return -1; | ||
} | ||
|
||
size_t executable_path_len = strlen(bundle_path) + strlen(executable) + 2; // +2 for '/' and '\0' | ||
char* executable_path = (char*)malloc(executable_path_len); | ||
size_t res = sprintf (executable_path, "%s/%s", bundle_path, executable); | ||
if (res != executable_path_len - 1) | ||
{ | ||
LOG_ERROR("Failed to resolve full path for: %s", executable); | ||
return -1; | ||
} | ||
executable_path[res] = '\0'; | ||
|
||
unsigned int coreclr_domainId = 0; | ||
void *coreclr_handle = NULL; | ||
|
||
LOG_INFO ("Calling coreclr_initialize"); | ||
int rv = coreclr_initialize ( | ||
executable_path, | ||
g_executable_path, | ||
executable, | ||
3, | ||
appctx_keys, | ||
appctx_values, | ||
&coreclr_handle, | ||
&coreclr_domainId | ||
&g_coreclr_handle, | ||
&g_coreclr_domainId | ||
); | ||
LOG_INFO ("coreclr_initialize returned %d", rv); | ||
return rv; | ||
} | ||
|
||
LOG_INFO ("Calling coreclr_execute_assembly"); | ||
coreclr_execute_assembly (coreclr_handle, coreclr_domainId, managed_argc, managed_argv, executable_path, &rv); | ||
void | ||
Java_net_dot_MonoRunner_setEnv (JNIEnv* env, jobject thiz, jstring j_key, jstring j_value) | ||
{ | ||
LOG_INFO ("Java_net_dot_MonoRunner_setEnv:"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would assert that coreclr hasn't been initialized yet. In fact, it might make sense to return a non-zero value if someone calls this and coreclr has been initialized. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it important that the runtime hasn't been initialized yet for setting env variables? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My assumption here is that this is primarily for setting environment variables for use during coreclr initialization, that could be wrong. The issue with that is there are some environment variables that aren't obvious when they are read/consumed and it can cause confusion if all On a general note for |
||
assert (g_coreclr_handle == NULL); // setenv should be only called before the runtime is initialized | ||
|
||
LOG_INFO ("Exit code: %d.", rv); | ||
return rv; | ||
const char *key = (*env)->GetStringUTFChars(env, j_key, 0); | ||
const char *val = (*env)->GetStringUTFChars(env, j_value, 0); | ||
|
||
LOG_INFO ("Setting env: %s=%s", key, val); | ||
setenv (key, val, true); | ||
(*env)->ReleaseStringUTFChars(env, j_key, key); | ||
(*env)->ReleaseStringUTFChars(env, j_value, val); | ||
} | ||
|
||
int | ||
Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_dir, jstring j_cache_dir, jstring j_testresults_dir, jstring j_entryPointLibName, jobjectArray j_args, long current_local_time) | ||
Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_dir, jstring j_entryPointLibName, long current_local_time) | ||
{ | ||
LOG_INFO ("Java_net_dot_MonoRunner_initRuntime:"); | ||
LOG_INFO ("Java_net_dot_MonoRunner_initRuntime (CoreCLR):"); | ||
char file_dir[2048]; | ||
char cache_dir[2048]; | ||
char testresults_dir[2048]; | ||
char entryPointLibName[2048]; | ||
strncpy_str (env, file_dir, j_files_dir, sizeof(file_dir)); | ||
strncpy_str (env, cache_dir, j_cache_dir, sizeof(cache_dir)); | ||
strncpy_str (env, testresults_dir, j_testresults_dir, sizeof(testresults_dir)); | ||
strncpy_str (env, entryPointLibName, j_entryPointLibName, sizeof(entryPointLibName)); | ||
|
||
bundle_path = file_dir; | ||
executable = entryPointLibName; | ||
size_t file_dir_len = strlen(file_dir); | ||
char* bundle_path_tmp = (char*)malloc(sizeof(char) * (file_dir_len + 1)); // +1 for '\0' | ||
if (bundle_path_tmp == NULL) | ||
{ | ||
LOG_ERROR("Failed to allocate memory for bundle_path"); | ||
return -1; | ||
} | ||
strncpy(bundle_path_tmp, file_dir, file_dir_len + 1); | ||
g_bundle_path = bundle_path_tmp; | ||
|
||
return mono_droid_runtime_init (entryPointLibName); | ||
} | ||
|
||
int | ||
Java_net_dot_MonoRunner_execEntryPoint (JNIEnv* env, jobject thiz, jstring j_entryPointLibName, jobjectArray j_args) | ||
{ | ||
LOG_INFO("Java_net_dot_MonoRunner_execEntryPoint (CoreCLR):"); | ||
|
||
if ((g_bundle_path == NULL) || (g_executable_path == NULL)) | ||
{ | ||
LOG_ERROR("Bundle path or executable path not set"); | ||
return -1; | ||
} | ||
|
||
setenv ("HOME", bundle_path, true); | ||
setenv ("TMPDIR", cache_dir, true); | ||
setenv ("TEST_RESULTS_DIR", testresults_dir, true); | ||
if ((g_coreclr_handle == NULL) || (g_coreclr_domainId == 0)) | ||
{ | ||
LOG_ERROR("CoreCLR not initialized"); | ||
return -1; | ||
} | ||
|
||
int args_len = (*env)->GetArrayLength(env, j_args); | ||
int args_len = (*env)->GetArrayLength (env, j_args); | ||
int managed_argc = args_len + 1; | ||
char** managed_argv = (char**)malloc(managed_argc * sizeof(char*)); | ||
const char** managed_argv = (const char**)malloc (managed_argc * sizeof(char*)); | ||
if (managed_argv == NULL) | ||
{ | ||
LOG_ERROR("Failed to allocate memory for managed_argv"); | ||
return -1; | ||
} | ||
|
||
managed_argv[0] = bundle_path; | ||
managed_argv[0] = g_bundle_path; | ||
for (int i = 0; i < args_len; ++i) | ||
{ | ||
jstring j_arg = (*env)->GetObjectArrayElement(env, j_args, i); | ||
managed_argv[i + 1] = (char*)((*env)->GetStringUTFChars(env, j_arg, NULL)); | ||
} | ||
|
||
int res = mono_droid_runtime_init (executable, managed_argc, managed_argv, current_local_time); | ||
int rv = mono_droid_execute_assembly (g_executable_path, g_coreclr_handle, g_coreclr_domainId, managed_argc, managed_argv); | ||
|
||
for (int i = 0; i < args_len; ++i) | ||
{ | ||
|
@@ -225,6 +301,12 @@ Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_ | |
} | ||
|
||
free(managed_argv); | ||
return res; | ||
return rv; | ||
} | ||
|
||
void | ||
Java_net_dot_MonoRunner_freeNativeResources (JNIEnv* env, jobject thiz) | ||
{ | ||
LOG_INFO ("Java_net_dot_MonoRunner_freeNativeResources (CoreCLR):"); | ||
free_resources (); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this splitting these actions across two threads now? I guess it is also possible this thread is also executing the handler below post other actions. I'm not sure the Android Activity life cycle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on my understanding, both
initializeRuntime
and the handler below will run on the same thread (cc: @jonathanpeppers). The motivation behind splitting and using the post handler is that theonCreate
from MainActivity can finish and report "Displayed..." to the logcat which is captured for TTID measurement. Currently, the TTID should include all the Java parts + theinitializeRuntime
. The post handler below will execute with a delay (afteronCreate
finished) and run the executable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wondered if you should remove the
new Handler(Looper.getMainLooper()).postDelayed(new Runnable()
call completely. .NET for Android apps do not callpost()
to run user code on startup.This also would make TTFD less important, and maybe you wouldn't even need to record that number. TTFD will include time Android spends doing things that we have little control over.
The reason you have for using
postDelayed()
is:We are doing this in the product in customers' apps, so seems reasonable to do it in this sample as well.
Removing
postDelayed()
could be done in another PR, though.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @jonathanpeppers for the comment and insights.
Indeed, .NET Android follows a different approach to how the runtime is initialized and code ran but I wasn't sure if the effort to rewrite the dotnet/runtime sample is worth for our use case. Splitting the initialization and execution of managed code seemed like a good compromise to get some meaningful information from TTID and TTFD. However, we could definitely create a proposal for the re-work if that's wanted.