+++ /dev/null
-CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
-PROJECT("dotnet-launcher")
-
-MESSAGE("CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
-
-IF(DEFINED NO_TIZEN)
- SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -DNO_TIZEN")
-ELSE(DEFINED NO_TIZEN)
- INCLUDE(FindPkgConfig)
- PKG_CHECK_MODULES(${PROJECT_NAME} REQUIRED aul dlog ecore bundle dlog launchpad)
-ENDIF(DEFINED NO_TIZEN)
-
-FOREACH(flag ${${PROJECT_NAME}_CFLAGS})
- SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
-ENDFOREACH(flag)
-
-IF(DEFINED LAUNCHER_PATH)
- SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -DLAUNCHER_PATH=${LAUNCHER_PATH}")
-ENDIF(DEFINED LAUNCHER_PATH)
-
-
-SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -std=c++11")
-SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Wl,-zdefs" )
-SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden")
-SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fPIE")
-SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fdata-sections -ffunction-sections -Wl,--gc-sections")
-SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -D_FILE_OFFSET_BITS=64")
-#SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -DLAUNCHING_TIME_MEASURE")
-
-SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}")
-SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
-SET(CMAKE_CXX_FLAGS_RELEASE "-O2")
-SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed")
-
-SET(${PROJECT_NAME}_SOURCE_FILES
- src/launcher.cc
- src/waiter.cc
-)
-
-ADD_EXECUTABLE(${PROJECT_NAME} ${${PROJECT_NAME}_SOURCE_FILES})
-
-IF(NOT DEFINED NO_TIZEN)
- TARGET_LINK_LIBRARIES(${PROJECT_NAME} aul)
-ENDIF(NOT DEFINED NO_TIZEN)
-TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${${PROJECT_NAME}_LDFLAGS} "-pie -ldl")
-
-SET_TARGET_PROPERTIES(${PROJECT_NAME}
- PROPERTIES SKIP_BUILD_RPATH TRUE
- ) # remove rpath option that is automatically generated by cmake.
-
-IF(NOT DEFINED NO_TIZEN)
- INSTALL(TARGETS ${PROJECT_NAME} DESTINATION ${BINDIR})
- INSTALL(FILES packaging/dotnet.loader DESTINATION ${LOADERDIR})
- INSTALL(FILES packaging/dotnet-launcher.ini DESTINATION ${CONFIGDIR})
-ENDIF(NOT DEFINED NO_TIZEN)
--- /dev/null
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+PROJECT("dotnet-launcher")
+
+MESSAGE("CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
+
+IF(DEFINED NO_TIZEN)
+ SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -DNO_TIZEN")
+ELSE(DEFINED NO_TIZEN)
+ INCLUDE(FindPkgConfig)
+ PKG_CHECK_MODULES(${PROJECT_NAME} REQUIRED aul dlog ecore bundle dlog launchpad)
+ENDIF(DEFINED NO_TIZEN)
+
+FOREACH(flag ${${PROJECT_NAME}_CFLAGS})
+ SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+IF(DEFINED LAUNCHER_PATH)
+ SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -DLAUNCHER_PATH=${LAUNCHER_PATH}")
+ENDIF(DEFINED LAUNCHER_PATH)
+
+
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -std=c++11")
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Wl,-zdefs" )
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden")
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fPIE")
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fdata-sections -ffunction-sections -Wl,--gc-sections")
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -D_FILE_OFFSET_BITS=64")
+#SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -DLAUNCHING_TIME_MEASURE")
+
+SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}")
+SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
+SET(CMAKE_CXX_FLAGS_RELEASE "-O2")
+SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed")
+
+SET(${PROJECT_NAME}_SOURCE_FILES
+ src/launcher.cc
+ src/waiter.cc
+)
+
+ADD_EXECUTABLE(${PROJECT_NAME} ${${PROJECT_NAME}_SOURCE_FILES})
+
+IF(NOT DEFINED NO_TIZEN)
+ TARGET_LINK_LIBRARIES(${PROJECT_NAME} aul)
+ENDIF(NOT DEFINED NO_TIZEN)
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${${PROJECT_NAME}_LDFLAGS} "-pie -ldl")
+
+SET_TARGET_PROPERTIES(${PROJECT_NAME}
+ PROPERTIES SKIP_BUILD_RPATH TRUE
+ ) # remove rpath option that is automatically generated by cmake.
+
+IF(NOT DEFINED NO_TIZEN)
+ INSTALL(TARGETS ${PROJECT_NAME} DESTINATION ${BINDIR})
+ INSTALL(FILES dotnet.loader DESTINATION ${LOADERDIR})
+ INSTALL(FILES dotnet-launcher.ini DESTINATION ${CONFIGDIR})
+ENDIF(NOT DEFINED NO_TIZEN)
--- /dev/null
+[dotnet]
+libcoreclr = libcoreclr.so
+coreclr_dir = /opt/usr/share/dotnet/shared/Microsoft.NETCore.App/1.0.0
+tpa_dirs = /opt/usr/share/dotnet/shared/Microsoft.NETCore.App/1.0.0:/usr/share/assembly
+native_so_search_dirs = /opt/usr/share/dotnet/shared/Microsoft.NETCore.App/1.0.0:/usr/share/assembly
--- /dev/null
+[LOADER]
+NAME dotnet-launcher
+EXE /usr/bin/dotnet-launcher
+APP_TYPE dotnet
+DETECTION_METHOD TIMEOUT|DEMAND
+TIMEOUT 5000
--- /dev/null
+#ifndef __ENVIRONMENT_H__
+
+#ifndef TIZEN_CORECLR_PATH
+#define TIZEN_CORECLR_PATH "/usr/share/coreclr/libcoreclr.so"
+#endif
+
+#ifndef TIZEN_NETCORE_TPA_PATH
+#define TIZEN_NETCORE_TPA_PATH "/usr/share/assemblies"
+#endif
+
+
+#endif // __ENVIRONMENT_H__
--- /dev/null
+#include <dlfcn.h>
+
+#ifdef LAUNCHING_TIME_MEASURE
+#include <ctime>
+#endif
+
+#include <cstdlib>
+#include <cstring>
+#include <vector>
+#include <string>
+#include <set>
+#include <sstream>
+#include <fstream>
+#include <memory>
+#include <algorithm>
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include "environment.h"
+#include "waiter.h"
+#include "log.h"
+
+#include "tini.hpp"
+
+#include "launcher.h"
+
+namespace dotnet {
+namespace runtime {
+
+#ifdef LAUNCHER_PATH
+#define __STR(x) #x
+#define __XSTR(x) __STR(x)
+#define LAUNCHER_CONFIG __XSTR(LAUNCHER_PATH)
+#else
+#define LAUNCHER_CONFIG "/etc/dotnet-launcher.ini"
+#endif
+static const std::string LauncherConfig(LAUNCHER_CONFIG);
+#ifdef LAUNCHER_PATH
+#undef __STR
+#undef __XSTR
+#undef LAUNCHER_CONFIG
+#undef LAUNCHER_PATH
+#endif
+
+#ifndef PATH_SEPARATOR
+#define PATH_SEPARATOR '/'
+#endif
+
+static std::string ConcatPath(const std::string& path1, const std::string& path2)
+{
+ std::string path(path1);
+ if (path.back() == PATH_SEPARATOR)
+ {
+ path.append(path2);
+ }
+ else
+ {
+ path += PATH_SEPARATOR;
+ path.append(path2);
+ }
+
+ return path;
+}
+static void AppendPath(std::string& path1, const std::string& path2)
+{
+ if (path1.back() == PATH_SEPARATOR)
+ {
+ path1.append(path2);
+ }
+ else
+ {
+ path1 += PATH_SEPARATOR;
+ path1.append(path2);
+ }
+}
+
+static std::string AbsolutePath(const std::string& path)
+{
+ std::string absPath;
+
+ char realPath[PATH_MAX];
+ if (realpath(path.c_str(), realPath) != nullptr && realPath[0] != '\0')
+ {
+ absPath.assign(realPath);
+ }
+
+ return absPath;
+}
+
+static std::string Basename(const std::string& path)
+{
+ auto pos = path.find_last_of(PATH_SEPARATOR);
+ if (pos != std::string::npos)
+ {
+ return path.substr(0, pos);
+ }
+ return path;
+}
+
+static bool EndWithIC(const std::string& str1, const std::string& str2, std::string& filename)
+{
+ std::string::size_type len1 = str1.length();
+ std::string::size_type len2 = str2.length();
+ if (len2 > len1) return false;
+
+ int i = 0;
+ bool result = std::all_of(str1.cend() - len2, str1.end(),
+ [&i, &str2] (char x) {
+ return std::tolower(x) == std::tolower(str2[i++]);
+ });
+ if (result)
+ {
+ filename = str1.substr(0, len1 - len2);
+ }
+ return result;
+}
+
+static std::string AssembliesInDirectory(const char *directory)
+{
+ const std::string ni = ".ni";
+ const std::string tpaExtensions[] =
+ {".ni.dll", ".dll", ".ni.exe", ".exe"};
+ const bool tpaExtensions_ni[] =
+ {true, false, true, false};
+
+ std::string tpaList;
+
+ DIR* dir = opendir(directory);
+
+ if (dir == nullptr)
+ {
+ _ERR("can not open directory : %s", directory);
+ return std::string();
+ }
+
+ std::set<std::string> addedAssemblies;
+
+ struct dirent *entry;
+
+ int tpaCount = sizeof(tpaExtensions) / sizeof(tpaExtensions[0]);
+ for (int i=0; i<tpaCount; i++)
+ {
+ const std::string& ext = tpaExtensions[i];
+ bool isNativeExt = tpaExtensions_ni[i];
+
+ while ((entry = readdir(dir)) != nullptr)
+ {
+ switch (entry->d_type)
+ {
+ case DT_REG: break;
+ case DT_LNK:
+ case DT_UNKNOWN:
+ {
+ std::string fullname;
+ fullname.append(directory);
+ fullname += PATH_SEPARATOR;
+ fullname.append(entry->d_name);
+
+ struct stat sb;
+ if (stat(fullname.c_str(), &sb) == -1)
+ continue;
+
+ if (!S_ISREG(sb.st_mode))
+ continue;
+ }
+ break;
+ default:
+ continue;
+ }
+
+ // Check the extension.
+ std::string filename(entry->d_name);
+ std::string filenameWithoutExt;
+ if (!EndWithIC(filename, ext, filenameWithoutExt))
+ continue;
+
+ if (!isNativeExt)
+ {
+ EndWithIC(filenameWithoutExt, ni, filenameWithoutExt);
+ }
+
+ if (addedAssemblies.find(filenameWithoutExt) == addedAssemblies.end())
+ {
+ addedAssemblies.insert(filenameWithoutExt);
+ std::string assembly;
+ assembly.append(directory);
+ assembly += PATH_SEPARATOR;
+ assembly.append(filename);
+ _DBG("TPA : %s", assembly.c_str());
+ tpaList += assembly + ':';
+ }
+ }
+ rewinddir(dir);
+ }
+
+ closedir(dir);
+ return tpaList;
+}
+
+Launcher::Launcher()
+{
+}
+
+Launcher::~Launcher()
+{
+}
+
+void Launcher::Initialize()
+{
+#ifdef LAUNCHING_TIME_MEASURE
+ std::clock_t start = std::clock();
+#endif
+
+ std::ifstream iniStream(LauncherConfig);
+ std::stringstream iniString;
+ iniString << iniStream.rdbuf();
+ tini::ini launcherIni(iniString);
+
+ _DBG("config file [%s]", LauncherConfig.c_str());
+ _DBG("%s", launcherIni.to_string().c_str());
+
+ auto &dotnet = launcherIni["dotnet"];
+ if (dotnet.find("libcoreclr") == dotnet.end())
+ dotnet["libcoreclr"] = "libcoreclr.so";
+ if (dotnet.find("coreclr_dir") == dotnet.end())
+ dotnet["coreclr_dir"] = "/usr/share/dotnet";
+ if (dotnet.find("tpa_dir") == dotnet.end())
+ dotnet["tpa_dir"] = dotnet["coreclr_dir"] + ":" + "/usr/share/assemblies";
+ if (dotnet.find("native_so_search_dirs") == dotnet.end())
+ dotnet["native_so_search_dirs"] = dotnet["tpa_dir"];
+
+ _DBG("libcoreclr : %s", dotnet["libcoreclr"].c_str());
+ _DBG("coreclr_dir : %s", dotnet["coreclr_dir"].c_str());
+ _DBG("tpa_dir : %s", dotnet["tpa_dir"].c_str());
+ _DBG("native_so_search_dirs : %s", dotnet["native_so_search_dirs"].c_str());
+
+ std::string libcoreclr = dotnet["coreclr_dir"] + "/" + dotnet["libcoreclr"];
+ std::string tpaDirs = dotnet["tpa_dirs"];
+ std::string nativeSoSearchDirs = dotnet["native_so_search_dirs"];
+
+ _DBG("libcoreclr.so : [%s]", libcoreclr.c_str());
+
+#ifdef LAUNCHING_TIME_MEASURE
+ std::clock_t config_read_time = std::clock();
+ _DBG("Reading Config file... : %Lf ms ", (config_read_time - start) / (double)(CLOCKS_PER_SEC / 1000));
+#endif
+
+ coreclrLib = dlopen(libcoreclr.c_str(), RTLD_NOW | RTLD_LOCAL);
+ if (coreclrLib == nullptr)
+ {
+ char *err = dlerror();
+ _ERR("dlopen failed to open libcoreclr.so with error %s", err);
+ return;
+ }
+
+ initializeCoreCLR = (coreclr_initialize_ptr)dlsym(coreclrLib, "coreclr_initialize");
+ executeAssembly = (coreclr_execute_assembly_ptr)dlsym(coreclrLib, "coreclr_execute_assembly");
+ shutdownCoreCLR = (coreclr_shutdown_ptr)dlsym(coreclrLib, "coreclr_shutdown");
+
+ if (initializeCoreCLR == nullptr)
+ {
+ _ERR("coreclr_initialize not found in the libcoreclr.so");
+ }
+ else if (executeAssembly == nullptr)
+ {
+ _ERR("coreclr_execute_assembly_ptr not found in the libcoreclr.so");
+ }
+ else if (shutdownCoreCLR == nullptr)
+ {
+ _ERR("coreclr_shutdown_ptr not found in the libcoreclr.so");
+ }
+ else
+ {
+ std::istringstream f(tpaDirs);
+ std::string s;
+ while (std::getline(f, s, ':'))
+ {
+ TrustedPlatformAssemblies += AssembliesInDirectory(s.c_str());
+ }
+ NativeDllSearchDirectories = nativeSoSearchDirs;
+ AppDomainCompatSwitch = "UseLatestBehaviorWhenTFMNotSpecified";
+ }
+
+#ifdef LAUNCHING_TIME_MEASURE
+ std::clock_t dlopen_time = std::clock();
+ _DBG("dlopen and dlsym time... : %Lf ms ", (dlopen_time - config_read_time) / (double)(CLOCKS_PER_SEC / 1000));
+ _DBG("initialize time... : %Lf ms ", (dlopen_time - start) / (double)(CLOCKS_PER_SEC / 1000));
+#endif
+
+}
+
+void Launcher::Launch(const string& exe_path, const string& app_root, int argc, char *argv[])
+{
+ std::string bin_path = Basename(AbsolutePath(exe_path));
+ std::string lib_path = ConcatPath(AbsolutePath(app_root), "lib");
+ std::string app_path = bin_path + ":" + lib_path;
+ std::string app_ni_path = app_path;
+ std::string nativeDllSearchDirectories = NativeDllSearchDirectories + app_path;
+
+ const char *propertyKeys[] =
+ {
+ "TRUSTED_PLATFORM_ASSEMBLIES",
+ "APP_PATHS",
+ "APP_NI_PATHS",
+ "NATIVE_DLL_SEARCH_DIRECTORIES",
+ "AppDomainCompatSwitch"
+ };
+
+ const char *propertyValues[] =
+ {
+ TrustedPlatformAssemblies.c_str(),
+ app_path.c_str(),
+ app_ni_path.c_str(),
+ nativeDllSearchDirectories.c_str(),
+ AppDomainCompatSwitch.c_str()
+ };
+
+ //_DBG("trusted platform assemblies : %s", propertyValues[0]);
+ _DBG("app_path : %s", propertyValues[1]);
+ _DBG("app_ni_path : %s", propertyValues[2]);
+ _DBG("native dll search path : %s", propertyValues[3]);
+ _DBG("app domain compat switch : %s", propertyValues[4]);
+
+ void* hostHandle;
+ unsigned int domainId;
+
+ _DBG("before initialize coreclr");
+
+#ifdef LAUNCHING_TIME_MEASURE
+ std::clock_t start = std::clock();
+#endif
+ int st = initializeCoreCLR(exe_path.c_str(),
+ "tizen_dotnet_launcher",
+ sizeof(propertyKeys) / sizeof(propertyKeys[0]),
+ propertyKeys,
+ propertyValues,
+ &hostHandle,
+ &domainId);
+
+ _DBG("after initialize coreclr");
+#ifdef LAUNCHING_TIME_MEASURE
+ std::clock_t initialize_coreclr_time = std::clock();
+ _DBG("call coreclr_initialize ... : %Lf ms ", (initialize_coreclr_time - start) / (double)(CLOCKS_PER_SEC / 1000));
+#endif
+ if (st < 0)
+ {
+ // initialize coreclr fail
+ _ERR("initialize core clr fail! (0x%08x)", st);
+ _DBG("check your smack label dll and every directories on the way to dlls");
+ }
+ else
+ {
+ unsigned int exitCode;
+ const char** argvc = const_cast<const char**>(argv);
+ st = executeAssembly(hostHandle, domainId, argc, argvc, exe_path.c_str(), &exitCode);
+ _DBG("after execute coreclr");
+ if (st < 0)
+ {
+ // execute coreclr fail
+ _ERR("execute core clr fail! (0x%08x / %d)", st, exitCode);
+ }
+ st = shutdownCoreCLR(hostHandle, domainId);
+ if (st < 0)
+ {
+ // shutdown fail
+ _ERR("shutdown core clr fail! (0x%08x)", st);
+ }
+#ifdef LAUNCHING_TIME_MEASURE
+ std::clock_t execute_assembly_time = std::clock();
+ _DBG("call execute_assembly_time ... : %Lf ms ", (execute_assembly_time - initialize_coreclr_time) / (double)(CLOCKS_PER_SEC / 1000));
+#endif
+ }
+ if (dlclose(coreclrLib) != 0)
+ {
+ _ERR("libcoreclr.so close failed");
+ }
+#ifdef LAUNCHING_TIME_MEASURE
+ std::clock_t after_launching_time = std::clock();
+ _DBG("launching end time ... : %Lf ms ", (after_launching_time - start) / (double)(CLOCKS_PER_SEC / 1000));
+#endif
+}
+
+} // namespace runtime
+} // namespace dotnet
+
+using dotnet::runtime::Launcher;
+using dotnet::runtime::Waiter;
+
+int main(int argc, char *argv[])
+{
+ _DBG("launcher started");
+
+ for (int i=0; i<argc; i++)
+ {
+ _DBG("argv[%d](%d) : [%s]", i, strlen(argv[i]), argv[i]);
+ }
+
+ std::unique_ptr<Launcher> launcher(new Launcher());
+ auto on_prepare = [&launcher]()
+ {
+ launcher->Initialize();
+ };
+ auto on_requested = [&launcher]()
+ {
+ };
+ auto on_executed = [&launcher](const std::string& path, const std::string& app_root, int argc, char *argv[])
+ {
+ _DBG("EXECUTE %s", path.c_str());
+ launcher->Launch(path, app_root, argc, argv);
+ };
+ std::unique_ptr<Waiter> waiter(new Waiter(on_prepare, on_requested, on_executed));
+ waiter->WaitToLaunching(argc, argv);
+}
--- /dev/null
+#include <string>
+#include <vector>
+
+namespace dotnet {
+namespace runtime {
+
+typedef int (*coreclr_initialize_ptr)(
+ const char* exePath,
+ const char* appDomainFriendlyName,
+ int propertyCount,
+ const char** propertyKeys,
+ const char** propertyValues,
+ void** hostHandle,
+ unsigned int* domainId);
+
+typedef int (*coreclr_execute_assembly_ptr)(
+ void* hostHandle,
+ unsigned int domainId,
+ int argc,
+ const char** argv,
+ const char* managedAssemblyPath,
+ unsigned int* exitCode);
+
+typedef int (*coreclr_shutdown_ptr)(
+ void* hostHandle,
+ unsigned int domainId);
+
+using std::string;
+using std::vector;
+
+class Launcher
+{
+ public:
+ Launcher();
+ ~Launcher();
+ void Initialize();
+ void Launch(const string& exe_path, const string& app_root, int argc, char *argv[]);
+
+ private:
+ coreclr_initialize_ptr initializeCoreCLR;
+ coreclr_execute_assembly_ptr executeAssembly;
+ coreclr_shutdown_ptr shutdownCoreCLR;
+
+ string TrustedPlatformAssemblies;
+ string NativeDllSearchDirectories;
+ string AppDomainCompatSwitch;
+
+ void *coreclrLib;
+};
+
+} // namespace runtime
+} // namespace dotnet
--- /dev/null
+#ifndef __LOG_H__
+#define __LOG_H__
+
+#ifndef NO_TIZEN
+#include <dlog.h>
+#else
+#include <stdio.h>
+#define LOGE(fmt, args...) printf(fmt, ##args)
+#define LOGD(fmt, args...) printf(fmt, ##args)
+#define LOGI(fmt, args...) printf(fmt, ##args)
+#endif
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "NETCORE_LAUNCHER"
+
+#ifndef _ERR
+#define _ERR(fmt, args...) LOGE(fmt "\n", ##args)
+#endif
+
+#ifndef _DBG
+#define _DBG(fmt, args...) LOGD(fmt "\n", ##args)
+#endif
+
+#ifndef _INFO
+#define _INFO(fmt, args...) LOGI(fmt "\n", ##args)
+#endif
+
+#endif /* __LOG_H__ */
--- /dev/null
+#include <string>
+#include <map>
+#include <vector>
+#include <sstream>
+#include <algorithm>
+#include <functional>
+
+namespace tini
+{
+
+const std::string global = "___GLOBAL";
+
+inline void ltrim(std::string& s)
+{
+ s.erase(s.begin(), std::find_if(s.begin(), s.end(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))));
+}
+
+inline void rtrim(std::string& s)
+{
+ s.erase(std::find_if(s.rbegin(), s.rend(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
+}
+
+inline void trim(std::string& s)
+{
+ ltrim(s);
+ rtrim(s);
+}
+
+inline int find_first_nonescaped(const std::string& str, char ch)
+{
+ if (!str.empty())
+ {
+ if (str[0] == ch) return 0;
+ for (size_t i=1; i<str.length(); i++)
+ {
+ if (str[i] == ch && str[i-1] != '\\')
+ return i;
+ }
+ }
+ return -1;
+}
+
+inline std::string uncomment(const std::string& str)
+{
+ int comment_start = find_first_nonescaped(str, '#');
+ if (comment_start < 0) return str;
+ return str.substr(0, comment_start);
+}
+
+inline bool get_header(const std::string& str, std::string& header)
+{
+ if (str.length() < 2 || str.front() != '[' || str.back() != ']') return false;
+ std::string expected = std::string(str.begin()+1, str.end()-1);
+ trim(expected);
+ if (expected.compare(header) != 0)
+ {
+ header = expected;
+ return true;
+ }
+ return false;
+}
+
+inline std::pair<std::string, std::string> get_pair(const std::string& str)
+{
+ std::string key, value;
+ std::string::size_type eq_pos = str.find('=');
+ if (eq_pos == std::string::npos)
+ {
+ key = str;
+ }
+ else
+ {
+ key = str.substr(0, eq_pos);
+ value = str.substr(eq_pos+1);
+ trim(value);
+ }
+ trim(key);
+ return std::pair<std::string, std::string>(key, value);
+}
+
+class ini
+{
+ public:
+ using pairs = std::map<std::string, std::string>;
+ ini()
+ {
+ }
+ ini(const std::string& str)
+ {
+ std::istringstream input(str);
+ init(input);
+ }
+ ini(std::istream &input)
+ {
+ init(input);
+ }
+ inline void init (std::istream &input)
+ {
+ std::string line;
+ std::string title(global);
+ while (std::getline(input, line))
+ {
+ line = uncomment(line);
+ if (line.empty()) continue;
+ trim(line);
+ if (get_header(line, title) && groups.find(title) == groups.end())
+ {
+ groups[title] = std::map<std::string, std::string>();
+ continue;
+ }
+
+ auto pair = get_pair(line);
+ groups[title][pair.first] = pair.second;
+ }
+ }
+ std::string to_string() const
+ {
+ std::stringstream str_out;
+ auto gpair = groups.find(global);
+ if (gpair != groups.end())
+ {
+ for (auto &pair : gpair->second)
+ {
+ str_out << pair.first << " = " << pair.second << std::endl;
+ }
+ }
+ for (auto &group : groups)
+ {
+ auto &title = group.first;
+ if (title == global) continue;
+ auto &pairs = group.second;
+ str_out << '[' << title << ']' << std::endl;
+ for (auto &pair : pairs)
+ {
+ str_out << pair.first << " = " << pair.second << std::endl;
+ }
+ }
+
+ return str_out.str();
+ }
+
+ pairs& operator [](const std::string& group_name)
+ {
+ if (groups.find(group_name) == groups.end())
+ {
+ groups[group_name] = pairs();
+ }
+ return groups[group_name];
+ }
+
+ std::map<std::string, pairs> groups;
+};
+
+std::ostream& operator << (std::ostream& os, const ini& rhs)
+{
+ os << rhs.to_string();
+ return os;
+}
+
+} // namespace tini
--- /dev/null
+
+#include <poll.h>
+
+#ifndef NO_TIZEN
+#include <launchpad.h>
+#include <aul.h>
+#endif
+
+#include <memory>
+#include <vector>
+#include <map>
+#include <poll.h>
+
+#include <iostream>
+
+#include "waiter.h"
+#include "log.h"
+
+namespace dotnet {
+namespace runtime {
+
+struct FdHandler
+{
+ pollfd *info;
+ Receiver receiver;
+};
+
+static volatile bool Waiting_;
+static std::vector<pollfd> Fdlist_;
+static std::map<int, FdHandler> Handlers_;
+static Waiter::AppInfo AppInfo_;
+
+void Waiter::OnPrepare()
+{
+ // preload the libraries.
+ if (prepare_ != nullptr)
+ prepare_();
+}
+
+void Waiter::OnLaunchRequested(const AppInfo& info)
+{
+ // do some job on user id is still system
+}
+
+void Waiter::OnWaiting()
+{
+ // Start the loop
+ Waiting_ = true;
+
+ _DBG("start polling...");
+ while (Waiting_)
+ {
+ if (poll(Fdlist_.data(), Fdlist_.size(), -1) < 0)
+ continue;
+
+ for (auto &p : Fdlist_)
+ {
+ if ( (p.revents | POLLIN) != 0 )
+ Handlers_[p.fd].receiver(p.fd);
+ }
+ }
+ _DBG("end polling...");
+}
+
+void Waiter::Stop()
+{
+ // Stop the loop
+
+ Waiting_ = false;
+}
+
+
+void Waiter::RegisterFd(int fd, Receiver receiver)
+{
+ // register fd should be caught in event loop
+
+ _DBG("Register fd %d", fd);
+
+ pollfd info;
+ info.fd = fd;
+ info.events = POLLIN;
+ info.revents = 0;
+
+ FdHandler handler;
+ Fdlist_.push_back(info);
+ handler.info = &Fdlist_.back();
+ handler.receiver = receiver;
+
+ Handlers_[fd] = handler;
+}
+
+void Waiter::DeregisterFd(int fd)
+{
+ // deregister fd should be caught in event loop
+
+ pollfd *info = Handlers_[fd].info;
+ Fdlist_.erase(Fdlist_.begin() - (info - &Fdlist_.front()));
+ Handlers_.erase(fd);
+}
+
+
+Waiter::Waiter(Action prepare, Action requested, Executor executor)
+{
+ prepare_ = prepare;
+ requested_ = requested;
+ executor_ = executor;
+}
+
+Waiter::~Waiter()
+{
+}
+
+int Waiter::WaitToLaunching(int argc, char *argv[])
+{
+#ifndef NO_TIZEN
+ auto on_create = [](bundle *extra, int type, void *user_data)
+ {
+ _DBG("on_create..."); // XXX
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->OnPrepare();
+ };
+
+ auto on_launch = [](int argc, char **argv, const char *app_path,
+ const char *appid, const char *pkgid,
+ const char *pkg_type, void *user_data) -> int
+ {
+ _DBG("on_launch..."); // XXX
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+
+ _DBG ("app path : %s", app_path);
+ _DBG ("app id : %s", appid);
+ _DBG ("pkg id : %s", pkgid);
+ _DBG ("pkg type : %s", pkg_type);
+
+ AppInfo info = {
+ AppPath : app_path,
+ AppId : appid,
+ PkgId : pkgid,
+ PkgType : pkg_type
+ };
+
+ waiter->OnLaunchRequested(info);
+ return 0;
+ };
+
+ auto on_terminate = [](int argc, char **argv, void *user_data) -> int
+ {
+ _DBG("on_terminate..."); // XXX
+
+ string app_root(aul_get_app_root_path());
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->executor_(argv[0], app_root, argc, argv);
+ return 0;
+ };
+
+ auto on_start_loop = [](void *user_data)
+ {
+ _DBG("on_start_loop..."); // XXX
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->OnWaiting();
+ };
+
+ auto on_quit_loop = [](void *user_data)
+ {
+ _DBG("on_quit_loop..."); // XXX
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->Stop();
+ };
+
+ auto on_add_fd = [](void *user_data, int fd, loader_receiver_cb receiver)
+ {
+ _DBG("on_add_fd..."); // XXX
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->RegisterFd(fd, receiver);
+ };
+
+ auto on_remove_fd = [](void *user_data, int fd)
+ {
+ _DBG("on_remove_fd..."); // XXX
+ Waiter* waiter = static_cast<Waiter*>(user_data);
+ waiter->DeregisterFd(fd);
+ };
+
+ _DBG("launcher wait..."); // XXX
+ loader_lifecycle_callback_s callbacks = {
+ .create = on_create,
+ .launch = on_launch,
+ .terminate = on_terminate
+ };
+
+ loader_adapter_s adapter = {
+ .loop_begin = on_start_loop,
+ .loop_quit = on_quit_loop,
+ .add_fd = on_add_fd,
+ .remove_fd = on_remove_fd
+ };
+
+ return launchpad_loader_main(argc, argv, &callbacks, &adapter, this);
+#else
+ if (argc < 2) return -1;
+ std::string app_path(argv[1]);
+ std::string app_root;
+ auto pos = app_path.find_last_of('/');
+ if (pos != std::string::npos)
+ app_root = app_path.substr(0, pos);
+ else
+ app_root = ".";
+
+ this->OnPrepare();
+ AppInfo info = {
+ AppPath : argv[1],
+ AppId : "",
+ PkgId : "",
+ PkgType : ""
+ };
+ this->OnLaunchRequested(info);
+ this->executor_(app_path, app_root, argc, argv);
+#endif
+}
+
+} // namespace runtime
+} // namespace dotnet
--- /dev/null
+#include <string>
+#include <functional>
+
+using std::string;
+
+namespace dotnet {
+namespace runtime {
+
+using Receiver = std::function<void(int)>;
+
+class Waiter
+{
+ public:
+ struct AppInfo
+ {
+ string AppPath;
+ string AppId;
+ string PkgId;
+ string PkgType;
+ };
+ using Action = std::function<void(void)>;
+ using Executor = std::function<void(const string&, const string&, int, char**)>;
+
+ Waiter(Action prepare, Action requested, Executor executor);
+ ~Waiter();
+
+ int WaitToLaunching(int argc, char *argv[]);
+ void Stop();
+
+ void RegisterFd(int fd, Receiver receiver);
+ void DeregisterFd(int fd);
+
+ protected:
+ void OnPrepare();
+ void OnLaunchRequested(const AppInfo&);
+ void OnWaiting();
+
+ private:
+
+ Action prepare_;
+ Action requested_;
+ Executor executor_;
+};
+
+} // namespace runtime
+} // namespace dotnet
--- /dev/null
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+
+ <PropertyGroup>
+ <CoreFXRefPath>/opt/usr/share/tizen.net/ref</CoreFXRefPath>
+ <TizenDeviceAPIPath>/usr/share/assembly</TizenDeviceAPIPath>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <NoCompilerStandardLib Condition=" '$(NoCompilerStandardLib)' == '' ">true</NoCompilerStandardLib>
+ <NoStdLib Condition=" '$(NoStdLib)' == '' ">true</NoStdLib>
+ <AdditionalLibPaths>$(CoreFXRefPath);$(TizenDeviceAPIPath)</AdditionalLibPaths>
+ <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>
+ <!-- Temporary suppress the warning... -->
+ <NoWarn>1701</NoWarn>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <CoreFXRefDir Include="$(CoreFXRefPath)" Condition="Exists('$(CoreFXRefPath)')"/>
+ <CoreFXRefAssemblies Include="@(CoreFXRefDir->'%(FullPath)/*.dll')" Exclude="@(CoreFXRefDir->'%(FullPath)/mscorlib.dll')"/>
+
+ <TizenDeviceAPIDir Include="$(TizenDeviceAPIPath)" Condition="Exists('$(TizenDeviceAPIPath)')"/>
+ <TizenDeviceAPIAssemblies Include="@(TizenDeviceAPIDir->'%(FullPath)/*.dll')"/>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ReferencePath Include="@(CoreFXRefAssemblies->'%(Filename).dll')"/>
+ <ReferencePath Include="@(TizenDeviceAPIAssemblies->'%(Filename).dll')"/>
+ </ItemGroup>
+
+ <Target Name="BeforeBuild">
+ <Message Text="[CoreFX Reference Environment!]"/>
+ <Message Text="CoreFX dir = $(CoreFXRefPath)"/>
+ <Message Text="CoreFX dir Check Ok" Condition="Exists('$(CoreFXRefPath)')"/>
+ <Message Text="Tizen Device API dir = $(TizenDeviceAPIPath)"/>
+ <Message Text="Tizen Device API Check Ok" Condition="Exists('$(TizenDeviceAPIPath)')"/>
+ <Message Text="CoreFXRefDir = @(CoreFXRefDir)"/>
+ <Message Text="TizenDeviceAPIDir = @(TizenDeviceAPIDir)"/>
+ <Message Text="CoreFXRefDir.Identity = %(CoreFXRefDir.Identity)"/>
+ <Message Text="TizenDeviceAPIDir.Identity = %(TizenDeviceAPIDir.Identity)"/>
+ <Message Text="CoreFXRefAssemblies = %(CoreFXRefAssemblies.Identity)"/>
+ <Message Text="TizenDeviceAPIAssemblies = %(TizenDeviceAPIAssemblies.Identity)"/>
+ </Target>
+
+ <PropertyGroup>
+ <CheckConfigDependsOn>CheckEnvironment</CheckConfigDependsOn>
+ </PropertyGroup>
+</Project>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<Project InitialTargets="CheckConfig" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <OutputType>Library</OutputType>
+ <AssemblyName>Tizen.Runtime</AssemblyName>
+ </PropertyGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug'">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <OutputPath>bin/</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ </PropertyGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release'">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin/</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <SignAssembly>true</SignAssembly>
+ <AssemblyOriginatorKeyFile>Tizen.Runtime.snk</AssemblyOriginatorKeyFile>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <Compile Include="Tizen.Runtime/AssemblyLoader.cs" />
+ <Compile Include="Tizen.Runtime/Log.cs" />
+ <Compile Include="Tizen.Runtime/AssemblyManager.cs" />
+ </ItemGroup>
+
+ <Target Name="CheckConfig">
+ <Message Text="MSBuildProjectDirectory = $(MSBuildProjectDirectory)"/>
+ </Target>
+
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="$(MSBuildProjectDirectory)/Tizen.CoreFX.Ref.Targets" />
+</Project>
--- /dev/null
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.Loader;
+using System.Collections.Generic;
+
+namespace Tizen.Runtime
+{
+ public class AssemblyLoader : AssemblyLoadContext
+ {
+ private SortedSet<string> _dllDirectories = new SortedSet<string>();
+ public IEnumerable<string> DllDirectories
+ {
+ get { return _dllDirectories; }
+ }
+
+ public void AddSearchableDirectory(string directory)
+ {
+ if (Directory.Exists(directory))
+ {
+ _dllDirectories.Add(directory);
+ }
+ }
+
+ public void RemoveSearchableDirectory(string directory)
+ {
+ _dllDirectories.Remove(directory);
+ }
+
+ protected override Assembly Load(AssemblyName assemblyName)
+ {
+ ALog.Debug($"Load!! : {assemblyName.Name}");
+ foreach (string dir in DllDirectories)
+ {
+ FileInfo f = new FileInfo(Path.Combine(dir, $"{assemblyName.Name}.dll"));
+ ALog.Debug(f.FullName);
+ if (File.Exists(f.FullName))
+ {
+ return LoadFromAssemblyPath(f.FullName);
+ }
+ }
+ return Assembly.Load(assemblyName);
+ }
+ }
+}
--- /dev/null
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.Loader;
+
+namespace Tizen.Runtime
+{
+ public static class AssemblyManager
+ {
+ private static void PrintException(Exception exception)
+ {
+ while (exception != null)
+ {
+ ALog.Debug(exception.Message);
+ ALog.Debug(exception.StackTrace);
+ exception = exception.InnerException;
+ }
+ }
+
+ public static AssemblyLoader CurrentAssemblyLoaderContext
+ {
+ get;
+ private set;
+ }
+
+ public static bool Initialize(string searchableDirectories, string preloadDllPaths)
+ {
+ try
+ {
+ CurrentAssemblyLoaderContext = new AssemblyLoader();
+
+ if (searchableDirectories != null)
+ {
+ string[] dirs = searchableDirectories.Split(':');
+ foreach (string dir in dirs)
+ {
+ CurrentAssemblyLoaderContext.AddSearchableDirectory(dir);
+ }
+ }
+
+ if (preloadDllPaths != null)
+ {
+ string[] dllPaths = preloadDllPaths.Split(':');
+ foreach (string dllPath in dllPaths)
+ {
+ FileInfo f = new FileInfo(dllPath);
+ if (File.Exists(f.FullName))
+ {
+ CurrentAssemblyLoaderContext.LoadFromAssemblyPath(f.FullName);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ ALog.Debug("Exception on Initialized");
+ PrintException(e);
+ return false;
+ }
+ return true;
+ }
+
+ public static void Execute(string dllPath)
+ {
+ try
+ {
+ FileInfo f = new FileInfo(dllPath);
+ if (File.Exists(f.FullName))
+ {
+ Assembly asm = CurrentAssemblyLoaderContext.LoadFromAssemblyPath(f.FullName);
+ if (asm == null) throw new FileNotFoundException($"{f.FullName} is not found");
+ if (asm.EntryPoint == null) throw new ArgumentException($"{f.FullName} did not have EntryPoint");
+ asm.EntryPoint.Invoke(null, new string[]{null});
+ }
+ }
+ catch (Exception e)
+ {
+ ALog.Debug("Exception on Execute");
+ PrintException(e);
+ }
+ }
+
+ public static void Finish()
+ {
+ }
+ }
+}
--- /dev/null
+using System;
+#if !CLOG
+using Tizen;
+#endif
+
+namespace Tizen.Runtime
+{
+#if CLOG
+ internal static class Log
+ {
+ static void Print(string tag, string message)
+ {
+ string[] lines = message.Split('\r');
+ foreach (string line in lines)
+ {
+ Console.WriteLine($"{tag} : {message}");
+ }
+ }
+
+ public static void Debug(string tag, string message)
+ {
+ Print($"D/{tag}", message);
+ }
+
+ public static void Info(string tag, string message)
+ {
+ Print($"I/{tag}", message);
+ }
+
+ public static void Error(string tag, string message)
+ {
+ Print($"E/{tag}", message);
+ }
+ }
+#endif
+ internal static class ALog
+ {
+ static string TAG = "Tizen.Runtime";
+
+ public static void Debug(string message)
+ {
+ Log.Debug(TAG, message);
+ }
+
+ public static void Info(string message)
+ {
+ Log.Info(TAG, message);
+ }
+
+ public static void Error(string message)
+ {
+ Log.Error(TAG, message);
+ }
+ }
+}
--- /dev/null
+using Tizen.Runtime;
+
+public class Program
+{
+ public static void Main(string[] args)
+ {
+ if (args.Length < 2)
+ {
+ ALog.Debug("Cancel..");
+ return;
+ }
+ string preload = Environment.GetEnvironmentVariable("PRELOAD_DLLS");
+ string searchable = String.Join(":", args, 1, args.Length - 1);
+
+ if (AssemblyManager.Initialize(searchable, preload))
+ {
+ ALog.Debug("After Initialized...");
+ AssemblyManager.Execute(args[0]);
+ }
+ }
+}
+++ /dev/null
-[dotnet]
-libcoreclr = libcoreclr.so
-coreclr_dir = /opt/usr/share/dotnet/shared/Microsoft.NETCore.App/1.0.0
-tpa_dirs = /opt/usr/share/dotnet/shared/Microsoft.NETCore.App/1.0.0:/usr/share/assembly
-native_so_search_dirs = /opt/usr/share/dotnet/shared/Microsoft.NETCore.App/1.0.0:/usr/share/assembly
BuildRequires: pkgconfig(ecore)
BuildRequires: pkgconfig(launchpad)
BuildRequires: aul-devel
+BuildRequires: corefx-managed-32b-ref
+BuildRequires: mono-compiler
+BuildRequires: mono-devel
+BuildRequires: csapi-tizen
Requires: aul
Requires(post): /sbin/ldconfig
-DLOADERDIR=%{_loaderdir} \
-DCONFIGDIR=%{_configdir} \
-DCMAKE_BUILD_TYPE=%{_buildmode} \
- -DVERSION=%{version}
+ -DVERSION=%{version} \
+ NativeLauncher
make %{?jobs:-j%jobs}
+xbuild /p:Configuration=%{_buildmode} Tizen.Runtime/Tizen.Runtime.csproj
+
%install
rm -rf %{buildroot}
%make_install
+install -p -m 644 Tizen.Runtime/bin/Tizen.Runtime.dll %{buildroot}%{_bindir}
%files
%manifest dotnet-launcher.manifest
%config /etc/dotnet-launcher.ini
%{_loaderdir}/dotnet.loader
%caps(cap_mac_admin,cap_setgid=ei) %{_bindir}/dotnet-launcher
+%caps(cap_mac_admin,cap_setgid=ei) %{_bindir}/Tizen.Runtime.dll
+++ /dev/null
-[LOADER]
-NAME dotnet-launcher
-EXE /usr/bin/dotnet-launcher
-APP_TYPE dotnet
-DETECTION_METHOD TIMEOUT|DEMAND
-TIMEOUT 5000
+++ /dev/null
-#ifndef __ENVIRONMENT_H__
-
-#ifndef TIZEN_CORECLR_PATH
-#define TIZEN_CORECLR_PATH "/usr/share/coreclr/libcoreclr.so"
-#endif
-
-#ifndef TIZEN_NETCORE_TPA_PATH
-#define TIZEN_NETCORE_TPA_PATH "/usr/share/assemblies"
-#endif
-
-
-#endif // __ENVIRONMENT_H__
+++ /dev/null
-#include <dlfcn.h>
-
-#ifdef LAUNCHING_TIME_MEASURE
-#include <ctime>
-#endif
-
-#include <cstdlib>
-#include <cstring>
-#include <vector>
-#include <string>
-#include <set>
-#include <sstream>
-#include <fstream>
-#include <memory>
-#include <algorithm>
-
-#include <dirent.h>
-#include <sys/stat.h>
-
-#include "environment.h"
-#include "waiter.h"
-#include "log.h"
-
-#include "tini.hpp"
-
-#include "launcher.h"
-
-namespace dotnet {
-namespace runtime {
-
-#ifdef LAUNCHER_PATH
-#define __STR(x) #x
-#define __XSTR(x) __STR(x)
-#define LAUNCHER_CONFIG __XSTR(LAUNCHER_PATH)
-#else
-#define LAUNCHER_CONFIG "/etc/dotnet-launcher.ini"
-#endif
-static const std::string LauncherConfig(LAUNCHER_CONFIG);
-#ifdef LAUNCHER_PATH
-#undef __STR
-#undef __XSTR
-#undef LAUNCHER_CONFIG
-#undef LAUNCHER_PATH
-#endif
-
-#ifndef PATH_SEPARATOR
-#define PATH_SEPARATOR '/'
-#endif
-
-static std::string ConcatPath(const std::string& path1, const std::string& path2)
-{
- std::string path(path1);
- if (path.back() == PATH_SEPARATOR)
- {
- path.append(path2);
- }
- else
- {
- path += PATH_SEPARATOR;
- path.append(path2);
- }
-
- return path;
-}
-static void AppendPath(std::string& path1, const std::string& path2)
-{
- if (path1.back() == PATH_SEPARATOR)
- {
- path1.append(path2);
- }
- else
- {
- path1 += PATH_SEPARATOR;
- path1.append(path2);
- }
-}
-
-static std::string AbsolutePath(const std::string& path)
-{
- std::string absPath;
-
- char realPath[PATH_MAX];
- if (realpath(path.c_str(), realPath) != nullptr && realPath[0] != '\0')
- {
- absPath.assign(realPath);
- }
-
- return absPath;
-}
-
-static std::string Basename(const std::string& path)
-{
- auto pos = path.find_last_of(PATH_SEPARATOR);
- if (pos != std::string::npos)
- {
- return path.substr(0, pos);
- }
- return path;
-}
-
-static bool EndWithIC(const std::string& str1, const std::string& str2, std::string& filename)
-{
- std::string::size_type len1 = str1.length();
- std::string::size_type len2 = str2.length();
- if (len2 > len1) return false;
-
- int i = 0;
- bool result = std::all_of(str1.cend() - len2, str1.end(),
- [&i, &str2] (char x) {
- return std::tolower(x) == std::tolower(str2[i++]);
- });
- if (result)
- {
- filename = str1.substr(0, len1 - len2);
- }
- return result;
-}
-
-static std::string AssembliesInDirectory(const char *directory)
-{
- const std::string ni = ".ni";
- const std::string tpaExtensions[] =
- {".ni.dll", ".dll", ".ni.exe", ".exe"};
- const bool tpaExtensions_ni[] =
- {true, false, true, false};
-
- std::string tpaList;
-
- DIR* dir = opendir(directory);
-
- if (dir == nullptr)
- {
- _ERR("can not open directory : %s", directory);
- return std::string();
- }
-
- std::set<std::string> addedAssemblies;
-
- struct dirent *entry;
-
- int tpaCount = sizeof(tpaExtensions) / sizeof(tpaExtensions[0]);
- for (int i=0; i<tpaCount; i++)
- {
- const std::string& ext = tpaExtensions[i];
- bool isNativeExt = tpaExtensions_ni[i];
-
- while ((entry = readdir(dir)) != nullptr)
- {
- switch (entry->d_type)
- {
- case DT_REG: break;
- case DT_LNK:
- case DT_UNKNOWN:
- {
- std::string fullname;
- fullname.append(directory);
- fullname += PATH_SEPARATOR;
- fullname.append(entry->d_name);
-
- struct stat sb;
- if (stat(fullname.c_str(), &sb) == -1)
- continue;
-
- if (!S_ISREG(sb.st_mode))
- continue;
- }
- break;
- default:
- continue;
- }
-
- // Check the extension.
- std::string filename(entry->d_name);
- std::string filenameWithoutExt;
- if (!EndWithIC(filename, ext, filenameWithoutExt))
- continue;
-
- if (!isNativeExt)
- {
- EndWithIC(filenameWithoutExt, ni, filenameWithoutExt);
- }
-
- if (addedAssemblies.find(filenameWithoutExt) == addedAssemblies.end())
- {
- addedAssemblies.insert(filenameWithoutExt);
- std::string assembly;
- assembly.append(directory);
- assembly += PATH_SEPARATOR;
- assembly.append(filename);
- _DBG("TPA : %s", assembly.c_str());
- tpaList += assembly + ':';
- }
- }
- rewinddir(dir);
- }
-
- closedir(dir);
- return tpaList;
-}
-
-Launcher::Launcher()
-{
-}
-
-Launcher::~Launcher()
-{
-}
-
-void Launcher::Initialize()
-{
-#ifdef LAUNCHING_TIME_MEASURE
- std::clock_t start = std::clock();
-#endif
-
- std::ifstream iniStream(LauncherConfig);
- std::stringstream iniString;
- iniString << iniStream.rdbuf();
- tini::ini launcherIni(iniString);
-
- _DBG("config file [%s]", LauncherConfig.c_str());
- _DBG("%s", launcherIni.to_string().c_str());
-
- auto &dotnet = launcherIni["dotnet"];
- if (dotnet.find("libcoreclr") == dotnet.end())
- dotnet["libcoreclr"] = "libcoreclr.so";
- if (dotnet.find("coreclr_dir") == dotnet.end())
- dotnet["coreclr_dir"] = "/usr/share/dotnet";
- if (dotnet.find("tpa_dir") == dotnet.end())
- dotnet["tpa_dir"] = dotnet["coreclr_dir"] + ":" + "/usr/share/assemblies";
- if (dotnet.find("native_so_search_dirs") == dotnet.end())
- dotnet["native_so_search_dirs"] = dotnet["tpa_dir"];
-
- _DBG("libcoreclr : %s", dotnet["libcoreclr"].c_str());
- _DBG("coreclr_dir : %s", dotnet["coreclr_dir"].c_str());
- _DBG("tpa_dir : %s", dotnet["tpa_dir"].c_str());
- _DBG("native_so_search_dirs : %s", dotnet["native_so_search_dirs"].c_str());
-
- std::string libcoreclr = dotnet["coreclr_dir"] + "/" + dotnet["libcoreclr"];
- std::string tpaDirs = dotnet["tpa_dirs"];
- std::string nativeSoSearchDirs = dotnet["native_so_search_dirs"];
-
- _DBG("libcoreclr.so : [%s]", libcoreclr.c_str());
-
-#ifdef LAUNCHING_TIME_MEASURE
- std::clock_t config_read_time = std::clock();
- _DBG("Reading Config file... : %Lf ms ", (config_read_time - start) / (double)(CLOCKS_PER_SEC / 1000));
-#endif
-
- coreclrLib = dlopen(libcoreclr.c_str(), RTLD_NOW | RTLD_LOCAL);
- if (coreclrLib == nullptr)
- {
- char *err = dlerror();
- _ERR("dlopen failed to open libcoreclr.so with error %s", err);
- return;
- }
-
- initializeCoreCLR = (coreclr_initialize_ptr)dlsym(coreclrLib, "coreclr_initialize");
- executeAssembly = (coreclr_execute_assembly_ptr)dlsym(coreclrLib, "coreclr_execute_assembly");
- shutdownCoreCLR = (coreclr_shutdown_ptr)dlsym(coreclrLib, "coreclr_shutdown");
-
- if (initializeCoreCLR == nullptr)
- {
- _ERR("coreclr_initialize not found in the libcoreclr.so");
- }
- else if (executeAssembly == nullptr)
- {
- _ERR("coreclr_execute_assembly_ptr not found in the libcoreclr.so");
- }
- else if (shutdownCoreCLR == nullptr)
- {
- _ERR("coreclr_shutdown_ptr not found in the libcoreclr.so");
- }
- else
- {
- std::istringstream f(tpaDirs);
- std::string s;
- while (std::getline(f, s, ':'))
- {
- TrustedPlatformAssemblies += AssembliesInDirectory(s.c_str());
- }
- NativeDllSearchDirectories = nativeSoSearchDirs;
- AppDomainCompatSwitch = "UseLatestBehaviorWhenTFMNotSpecified";
- }
-
-#ifdef LAUNCHING_TIME_MEASURE
- std::clock_t dlopen_time = std::clock();
- _DBG("dlopen and dlsym time... : %Lf ms ", (dlopen_time - config_read_time) / (double)(CLOCKS_PER_SEC / 1000));
- _DBG("initialize time... : %Lf ms ", (dlopen_time - start) / (double)(CLOCKS_PER_SEC / 1000));
-#endif
-
-}
-
-void Launcher::Launch(const string& exe_path, const string& app_root, int argc, char *argv[])
-{
- std::string bin_path = Basename(AbsolutePath(exe_path));
- std::string lib_path = ConcatPath(AbsolutePath(app_root), "lib");
- std::string app_path = bin_path + ":" + lib_path;
- std::string app_ni_path = app_path;
- std::string nativeDllSearchDirectories = NativeDllSearchDirectories + app_path;
-
- const char *propertyKeys[] =
- {
- "TRUSTED_PLATFORM_ASSEMBLIES",
- "APP_PATHS",
- "APP_NI_PATHS",
- "NATIVE_DLL_SEARCH_DIRECTORIES",
- "AppDomainCompatSwitch"
- };
-
- const char *propertyValues[] =
- {
- TrustedPlatformAssemblies.c_str(),
- app_path.c_str(),
- app_ni_path.c_str(),
- nativeDllSearchDirectories.c_str(),
- AppDomainCompatSwitch.c_str()
- };
-
- //_DBG("trusted platform assemblies : %s", propertyValues[0]);
- _DBG("app_path : %s", propertyValues[1]);
- _DBG("app_ni_path : %s", propertyValues[2]);
- _DBG("native dll search path : %s", propertyValues[3]);
- _DBG("app domain compat switch : %s", propertyValues[4]);
-
- void* hostHandle;
- unsigned int domainId;
-
- _DBG("before initialize coreclr");
-
-#ifdef LAUNCHING_TIME_MEASURE
- std::clock_t start = std::clock();
-#endif
- int st = initializeCoreCLR(exe_path.c_str(),
- "tizen_dotnet_launcher",
- sizeof(propertyKeys) / sizeof(propertyKeys[0]),
- propertyKeys,
- propertyValues,
- &hostHandle,
- &domainId);
-
- _DBG("after initialize coreclr");
-#ifdef LAUNCHING_TIME_MEASURE
- std::clock_t initialize_coreclr_time = std::clock();
- _DBG("call coreclr_initialize ... : %Lf ms ", (initialize_coreclr_time - start) / (double)(CLOCKS_PER_SEC / 1000));
-#endif
- if (st < 0)
- {
- // initialize coreclr fail
- _ERR("initialize core clr fail! (0x%08x)", st);
- _DBG("check your smack label dll and every directories on the way to dlls");
- }
- else
- {
- unsigned int exitCode;
- const char** argvc = const_cast<const char**>(argv);
- st = executeAssembly(hostHandle, domainId, argc, argvc, exe_path.c_str(), &exitCode);
- _DBG("after execute coreclr");
- if (st < 0)
- {
- // execute coreclr fail
- _ERR("execute core clr fail! (0x%08x / %d)", st, exitCode);
- }
- st = shutdownCoreCLR(hostHandle, domainId);
- if (st < 0)
- {
- // shutdown fail
- _ERR("shutdown core clr fail! (0x%08x)", st);
- }
-#ifdef LAUNCHING_TIME_MEASURE
- std::clock_t execute_assembly_time = std::clock();
- _DBG("call execute_assembly_time ... : %Lf ms ", (execute_assembly_time - initialize_coreclr_time) / (double)(CLOCKS_PER_SEC / 1000));
-#endif
- }
- if (dlclose(coreclrLib) != 0)
- {
- _ERR("libcoreclr.so close failed");
- }
-#ifdef LAUNCHING_TIME_MEASURE
- std::clock_t after_launching_time = std::clock();
- _DBG("launching end time ... : %Lf ms ", (after_launching_time - start) / (double)(CLOCKS_PER_SEC / 1000));
-#endif
-}
-
-} // namespace runtime
-} // namespace dotnet
-
-using dotnet::runtime::Launcher;
-using dotnet::runtime::Waiter;
-
-int main(int argc, char *argv[])
-{
- _DBG("launcher started");
-
- for (int i=0; i<argc; i++)
- {
- _DBG("argv[%d](%d) : [%s]", i, strlen(argv[i]), argv[i]);
- }
-
- std::unique_ptr<Launcher> launcher(new Launcher());
- auto on_prepare = [&launcher]()
- {
- launcher->Initialize();
- };
- auto on_requested = [&launcher]()
- {
- };
- auto on_executed = [&launcher](const std::string& path, const std::string& app_root, int argc, char *argv[])
- {
- _DBG("EXECUTE %s", path.c_str());
- launcher->Launch(path, app_root, argc, argv);
- };
- std::unique_ptr<Waiter> waiter(new Waiter(on_prepare, on_requested, on_executed));
- waiter->WaitToLaunching(argc, argv);
-}
+++ /dev/null
-#include <string>
-#include <vector>
-
-namespace dotnet {
-namespace runtime {
-
-typedef int (*coreclr_initialize_ptr)(
- const char* exePath,
- const char* appDomainFriendlyName,
- int propertyCount,
- const char** propertyKeys,
- const char** propertyValues,
- void** hostHandle,
- unsigned int* domainId);
-
-typedef int (*coreclr_execute_assembly_ptr)(
- void* hostHandle,
- unsigned int domainId,
- int argc,
- const char** argv,
- const char* managedAssemblyPath,
- unsigned int* exitCode);
-
-typedef int (*coreclr_shutdown_ptr)(
- void* hostHandle,
- unsigned int domainId);
-
-using std::string;
-using std::vector;
-
-class Launcher
-{
- public:
- Launcher();
- ~Launcher();
- void Initialize();
- void Launch(const string& exe_path, const string& app_root, int argc, char *argv[]);
-
- private:
- coreclr_initialize_ptr initializeCoreCLR;
- coreclr_execute_assembly_ptr executeAssembly;
- coreclr_shutdown_ptr shutdownCoreCLR;
-
- string TrustedPlatformAssemblies;
- string NativeDllSearchDirectories;
- string AppDomainCompatSwitch;
-
- void *coreclrLib;
-};
-
-} // namespace runtime
-} // namespace dotnet
+++ /dev/null
-#ifndef __LOG_H__
-#define __LOG_H__
-
-#ifndef NO_TIZEN
-#include <dlog.h>
-#else
-#include <stdio.h>
-#define LOGE(fmt, args...) printf(fmt, ##args)
-#define LOGD(fmt, args...) printf(fmt, ##args)
-#define LOGI(fmt, args...) printf(fmt, ##args)
-#endif
-
-#ifdef LOG_TAG
-#undef LOG_TAG
-#endif
-#define LOG_TAG "NETCORE_LAUNCHER"
-
-#ifndef _ERR
-#define _ERR(fmt, args...) LOGE(fmt "\n", ##args)
-#endif
-
-#ifndef _DBG
-#define _DBG(fmt, args...) LOGD(fmt "\n", ##args)
-#endif
-
-#ifndef _INFO
-#define _INFO(fmt, args...) LOGI(fmt "\n", ##args)
-#endif
-
-#endif /* __LOG_H__ */
+++ /dev/null
-#include <string>
-#include <map>
-#include <vector>
-#include <sstream>
-#include <algorithm>
-#include <functional>
-
-namespace tini
-{
-
-const std::string global = "___GLOBAL";
-
-inline void ltrim(std::string& s)
-{
- s.erase(s.begin(), std::find_if(s.begin(), s.end(),
- std::not1(std::ptr_fun<int, int>(std::isspace))));
-}
-
-inline void rtrim(std::string& s)
-{
- s.erase(std::find_if(s.rbegin(), s.rend(),
- std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
-}
-
-inline void trim(std::string& s)
-{
- ltrim(s);
- rtrim(s);
-}
-
-inline int find_first_nonescaped(const std::string& str, char ch)
-{
- if (!str.empty())
- {
- if (str[0] == ch) return 0;
- for (size_t i=1; i<str.length(); i++)
- {
- if (str[i] == ch && str[i-1] != '\\')
- return i;
- }
- }
- return -1;
-}
-
-inline std::string uncomment(const std::string& str)
-{
- int comment_start = find_first_nonescaped(str, '#');
- if (comment_start < 0) return str;
- return str.substr(0, comment_start);
-}
-
-inline bool get_header(const std::string& str, std::string& header)
-{
- if (str.length() < 2 || str.front() != '[' || str.back() != ']') return false;
- std::string expected = std::string(str.begin()+1, str.end()-1);
- trim(expected);
- if (expected.compare(header) != 0)
- {
- header = expected;
- return true;
- }
- return false;
-}
-
-inline std::pair<std::string, std::string> get_pair(const std::string& str)
-{
- std::string key, value;
- std::string::size_type eq_pos = str.find('=');
- if (eq_pos == std::string::npos)
- {
- key = str;
- }
- else
- {
- key = str.substr(0, eq_pos);
- value = str.substr(eq_pos+1);
- trim(value);
- }
- trim(key);
- return std::pair<std::string, std::string>(key, value);
-}
-
-class ini
-{
- public:
- using pairs = std::map<std::string, std::string>;
- ini()
- {
- }
- ini(const std::string& str)
- {
- std::istringstream input(str);
- init(input);
- }
- ini(std::istream &input)
- {
- init(input);
- }
- inline void init (std::istream &input)
- {
- std::string line;
- std::string title(global);
- while (std::getline(input, line))
- {
- line = uncomment(line);
- if (line.empty()) continue;
- trim(line);
- if (get_header(line, title) && groups.find(title) == groups.end())
- {
- groups[title] = std::map<std::string, std::string>();
- continue;
- }
-
- auto pair = get_pair(line);
- groups[title][pair.first] = pair.second;
- }
- }
- std::string to_string() const
- {
- std::stringstream str_out;
- auto gpair = groups.find(global);
- if (gpair != groups.end())
- {
- for (auto &pair : gpair->second)
- {
- str_out << pair.first << " = " << pair.second << std::endl;
- }
- }
- for (auto &group : groups)
- {
- auto &title = group.first;
- if (title == global) continue;
- auto &pairs = group.second;
- str_out << '[' << title << ']' << std::endl;
- for (auto &pair : pairs)
- {
- str_out << pair.first << " = " << pair.second << std::endl;
- }
- }
-
- return str_out.str();
- }
-
- pairs& operator [](const std::string& group_name)
- {
- if (groups.find(group_name) == groups.end())
- {
- groups[group_name] = pairs();
- }
- return groups[group_name];
- }
-
- std::map<std::string, pairs> groups;
-};
-
-std::ostream& operator << (std::ostream& os, const ini& rhs)
-{
- os << rhs.to_string();
- return os;
-}
-
-} // namespace tini
+++ /dev/null
-
-#include <poll.h>
-
-#ifndef NO_TIZEN
-#include <launchpad.h>
-#include <aul.h>
-#endif
-
-#include <memory>
-#include <vector>
-#include <map>
-#include <poll.h>
-
-#include <iostream>
-
-#include "waiter.h"
-#include "log.h"
-
-namespace dotnet {
-namespace runtime {
-
-struct FdHandler
-{
- pollfd *info;
- Receiver receiver;
-};
-
-static volatile bool Waiting_;
-static std::vector<pollfd> Fdlist_;
-static std::map<int, FdHandler> Handlers_;
-static Waiter::AppInfo AppInfo_;
-
-void Waiter::OnPrepare()
-{
- // preload the libraries.
- if (prepare_ != nullptr)
- prepare_();
-}
-
-void Waiter::OnLaunchRequested(const AppInfo& info)
-{
- // do some job on user id is still system
-}
-
-void Waiter::OnWaiting()
-{
- // Start the loop
- Waiting_ = true;
-
- _DBG("start polling...");
- while (Waiting_)
- {
- if (poll(Fdlist_.data(), Fdlist_.size(), -1) < 0)
- continue;
-
- for (auto &p : Fdlist_)
- {
- if ( (p.revents | POLLIN) != 0 )
- Handlers_[p.fd].receiver(p.fd);
- }
- }
- _DBG("end polling...");
-}
-
-void Waiter::Stop()
-{
- // Stop the loop
-
- Waiting_ = false;
-}
-
-
-void Waiter::RegisterFd(int fd, Receiver receiver)
-{
- // register fd should be caught in event loop
-
- _DBG("Register fd %d", fd);
-
- pollfd info;
- info.fd = fd;
- info.events = POLLIN;
- info.revents = 0;
-
- FdHandler handler;
- Fdlist_.push_back(info);
- handler.info = &Fdlist_.back();
- handler.receiver = receiver;
-
- Handlers_[fd] = handler;
-}
-
-void Waiter::DeregisterFd(int fd)
-{
- // deregister fd should be caught in event loop
-
- pollfd *info = Handlers_[fd].info;
- Fdlist_.erase(Fdlist_.begin() - (info - &Fdlist_.front()));
- Handlers_.erase(fd);
-}
-
-
-Waiter::Waiter(Action prepare, Action requested, Executor executor)
-{
- prepare_ = prepare;
- requested_ = requested;
- executor_ = executor;
-}
-
-Waiter::~Waiter()
-{
-}
-
-int Waiter::WaitToLaunching(int argc, char *argv[])
-{
-#ifndef NO_TIZEN
- auto on_create = [](bundle *extra, int type, void *user_data)
- {
- _DBG("on_create..."); // XXX
- Waiter* waiter = static_cast<Waiter*>(user_data);
- waiter->OnPrepare();
- };
-
- auto on_launch = [](int argc, char **argv, const char *app_path,
- const char *appid, const char *pkgid,
- const char *pkg_type, void *user_data) -> int
- {
- _DBG("on_launch..."); // XXX
- Waiter* waiter = static_cast<Waiter*>(user_data);
-
- _DBG ("app path : %s", app_path);
- _DBG ("app id : %s", appid);
- _DBG ("pkg id : %s", pkgid);
- _DBG ("pkg type : %s", pkg_type);
-
- AppInfo info = {
- AppPath : app_path,
- AppId : appid,
- PkgId : pkgid,
- PkgType : pkg_type
- };
-
- waiter->OnLaunchRequested(info);
- return 0;
- };
-
- auto on_terminate = [](int argc, char **argv, void *user_data) -> int
- {
- _DBG("on_terminate..."); // XXX
-
- string app_root(aul_get_app_root_path());
- Waiter* waiter = static_cast<Waiter*>(user_data);
- waiter->executor_(argv[0], app_root, argc, argv);
- return 0;
- };
-
- auto on_start_loop = [](void *user_data)
- {
- _DBG("on_start_loop..."); // XXX
- Waiter* waiter = static_cast<Waiter*>(user_data);
- waiter->OnWaiting();
- };
-
- auto on_quit_loop = [](void *user_data)
- {
- _DBG("on_quit_loop..."); // XXX
- Waiter* waiter = static_cast<Waiter*>(user_data);
- waiter->Stop();
- };
-
- auto on_add_fd = [](void *user_data, int fd, loader_receiver_cb receiver)
- {
- _DBG("on_add_fd..."); // XXX
- Waiter* waiter = static_cast<Waiter*>(user_data);
- waiter->RegisterFd(fd, receiver);
- };
-
- auto on_remove_fd = [](void *user_data, int fd)
- {
- _DBG("on_remove_fd..."); // XXX
- Waiter* waiter = static_cast<Waiter*>(user_data);
- waiter->DeregisterFd(fd);
- };
-
- _DBG("launcher wait..."); // XXX
- loader_lifecycle_callback_s callbacks = {
- .create = on_create,
- .launch = on_launch,
- .terminate = on_terminate
- };
-
- loader_adapter_s adapter = {
- .loop_begin = on_start_loop,
- .loop_quit = on_quit_loop,
- .add_fd = on_add_fd,
- .remove_fd = on_remove_fd
- };
-
- return launchpad_loader_main(argc, argv, &callbacks, &adapter, this);
-#else
- if (argc < 2) return -1;
- std::string app_path(argv[1]);
- std::string app_root;
- auto pos = app_path.find_last_of('/');
- if (pos != std::string::npos)
- app_root = app_path.substr(0, pos);
- else
- app_root = ".";
-
- this->OnPrepare();
- AppInfo info = {
- AppPath : argv[1],
- AppId : "",
- PkgId : "",
- PkgType : ""
- };
- this->OnLaunchRequested(info);
- this->executor_(app_path, app_root, argc, argv);
-#endif
-}
-
-} // namespace runtime
-} // namespace dotnet
+++ /dev/null
-#include <string>
-#include <functional>
-
-using std::string;
-
-namespace dotnet {
-namespace runtime {
-
-using Receiver = std::function<void(int)>;
-
-class Waiter
-{
- public:
- struct AppInfo
- {
- string AppPath;
- string AppId;
- string PkgId;
- string PkgType;
- };
- using Action = std::function<void(void)>;
- using Executor = std::function<void(const string&, const string&, int, char**)>;
-
- Waiter(Action prepare, Action requested, Executor executor);
- ~Waiter();
-
- int WaitToLaunching(int argc, char *argv[]);
- void Stop();
-
- void RegisterFd(int fd, Receiver receiver);
- void DeregisterFd(int fd);
-
- protected:
- void OnPrepare();
- void OnLaunchRequested(const AppInfo&);
- void OnWaiting();
-
- private:
-
- Action prepare_;
- Action requested_;
- Executor executor_;
-};
-
-} // namespace runtime
-} // namespace dotnet