//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// #include "BsMonoManager.h" #include "Error/BsException.h" #include "BsScriptMeta.h" #include "BsMonoAssembly.h" #include "BsMonoClass.h" #include "BsMonoUtil.h" #include "FileSystem/BsFileSystem.h" #include "Error/BsException.h" #include "BsApplication.h" #include "mono/jit/jit.h" #include #include #include #include #include #include namespace bs { const String MONO_LIB_DIR = "bin/Mono/lib/"; const String MONO_ETC_DIR = "bin/Mono/etc/"; const String MONO_COMPILER_DIR = "bin/Mono/compiler/"; const MonoVersion MONO_VERSION = MonoVersion::v4_5; struct MonoVersionData { String path; String version; }; static const MonoVersionData MONO_VERSION_DATA[1] = { { MONO_LIB_DIR + "mono/4.5", "v4.0.30319" } }; void monoLogCallback(const char* logDomain, const char* logLevel, const char* message, mono_bool fatal, void* userData) { static const char* monoErrorLevels[] = { nullptr, "error", "critical", "warning", "message", "info", "debug" }; UINT32 errorLevel = 0; if (logLevel != nullptr) { for (UINT32 i = 1; i < 7; i++) { if (strcmp(monoErrorLevels[i], logLevel) == 0) { errorLevel = i; break; } } } if (errorLevel == 0) { LOGERR(StringUtil::format("Mono: {0} in domain {1}", message, logDomain)); } else if (errorLevel <= 2) { LOGERR(StringUtil::format("Mono: {0} in domain {1} [{2}]", message, logDomain, logLevel)); } else if (errorLevel <= 3) { LOGWRN(StringUtil::format("Mono: {0} in domain {1} [{2}]", message, logDomain, logLevel)); } else { LOGDBG(StringUtil::format("Mono: {0} in domain {1} [{2}]", message, logDomain, logLevel)); } } void monoPrintCallback(const char* string, mono_bool isStdout) { LOGWRN(StringUtil::format("Mono error: {0}", string)); } void monoPrintErrorCallback(const char* string, mono_bool isStdout) { LOGERR(StringUtil::format("Mono error: {0}", string)); } MonoManager::MonoManager() :mScriptDomain(nullptr), mRootDomain(nullptr), mCorlibAssembly(nullptr) { Path libDir = Paths::findPath(MONO_LIB_DIR); Path etcDir = getMonoEtcFolder(); Path assembliesDir = getFrameworkAssembliesFolder(); mono_set_dirs(libDir.toString().c_str(), etcDir.toString().c_str()); mono_set_assemblies_path(assembliesDir.toString().c_str()); #if BS_DEBUG_MODE // Note: For proper debugging experience make sure to open a console window to display stdout and stderr, as Mono // uses them for much of its logging. mono_debug_init(MONO_DEBUG_FORMAT_MONO); const char* options[] = { "--soft-breakpoints", "--debugger-agent=transport=dt_socket,address=127.0.0.1:17615,embedding=1,server=y,suspend=n", "--debug-domain-unload", // GC options: // check-remset-consistency: Makes sure that write barriers are properly issued in native code, and therefore // all old->new generation references are properly present in the remset. This is easy to mess up in native // code by performing a simple memory copy without a barrier, so it's important to keep the option on. // verify-before-collections: Unusure what exactly it does, but it sounds like it could help track down // things like accessing released/moved objects, or attempting to release handles for an unloaded domain. // xdomain-checks: Makes sure that no references are left when a domain is unloaded. "--gc-debug=check-remset-consistency,verify-before-collections,xdomain-checks" }; mono_jit_parse_options(4, (char**)options); mono_trace_set_level_string("warning"); // Note: Switch to "debug" for detailed output, disabled for now due to spam #else mono_trace_set_level_string("warning"); #endif mono_trace_set_log_handler(monoLogCallback, this); mono_trace_set_print_handler(monoPrintCallback); mono_trace_set_printerr_handler(monoPrintErrorCallback); mono_config_parse(nullptr); mRootDomain = mono_jit_init_version("BansheeMono", MONO_VERSION_DATA[(int)MONO_VERSION].version.c_str()); if (mRootDomain == nullptr) BS_EXCEPT(InternalErrorException, "Cannot initialize Mono runtime."); mono_thread_set_main(mono_thread_current()); // Load corlib mCorlibAssembly = new (bs_alloc()) MonoAssembly(L"corlib", "corlib"); mCorlibAssembly->loadFromImage(mono_get_corlib()); mAssemblies["corlib"] = mCorlibAssembly; } MonoManager::~MonoManager() { for (auto& entry : mAssemblies) { bs_delete(entry.second); } mAssemblies.clear(); unloadScriptDomain(); if (mRootDomain != nullptr) { mono_jit_cleanup(mRootDomain); mRootDomain = nullptr; } // Make sure to explicitly clear this meta-data, as it contains structures allocated from other dynamic libraries, // which will likely get unloaded right after shutdown getScriptMetaData().clear(); } MonoAssembly& MonoManager::loadAssembly(const WString& path, const String& name) { MonoAssembly* assembly = nullptr; if (mScriptDomain == nullptr) { String appDomainName = "ScriptDomain"; mScriptDomain = mono_domain_create_appdomain(const_cast(appDomainName.c_str()), nullptr); if (mScriptDomain == nullptr) BS_EXCEPT(InternalErrorException, "Cannot create script app domain."); if(!mono_domain_set(mScriptDomain, true)) BS_EXCEPT(InternalErrorException, "Cannot set script app domain."); } auto iterFind = mAssemblies.find(name); if(iterFind != mAssemblies.end()) { assembly = iterFind->second; } else { assembly = new (bs_alloc()) MonoAssembly(path, name); mAssemblies[name] = assembly; } initializeAssembly(*assembly); return *assembly; } void MonoManager::initializeAssembly(MonoAssembly& assembly) { if (!assembly.mIsLoaded) { assembly.load(); // Fully initialize all types that use this assembly Vector& typeMetas = getScriptMetaData()[assembly.mName]; for (auto& entry : typeMetas) { ScriptMeta* meta = entry.metaData; *meta = entry.localMetaData; meta->scriptClass = assembly.getClass(meta->ns, meta->name); if (meta->scriptClass == nullptr) { BS_EXCEPT(InvalidParametersException, "Unable to find class of type: \"" + meta->ns + "::" + meta->name + "\""); } if (meta->scriptClass->hasField("mCachedPtr")) meta->thisPtrField = meta->scriptClass->getField("mCachedPtr"); else meta->thisPtrField = nullptr; meta->initCallback(); } } } MonoAssembly* MonoManager::getAssembly(const String& name) const { auto iterFind = mAssemblies.find(name); if(iterFind != mAssemblies.end()) return iterFind->second; return nullptr; } void MonoManager::registerScriptType(ScriptMeta* metaData, const ScriptMeta& localMetaData) { Vector& mMetas = getScriptMetaData()[localMetaData.assembly]; mMetas.push_back({ metaData, localMetaData }); } MonoClass* MonoManager::findClass(const String& ns, const String& typeName) { MonoClass* monoClass = nullptr; for(auto& assembly : mAssemblies) { monoClass = assembly.second->getClass(ns, typeName); if(monoClass != nullptr) return monoClass; } return nullptr; } MonoClass* MonoManager::findClass(::MonoClass* rawMonoClass) { MonoClass* monoClass = nullptr; for(auto& assembly : mAssemblies) { monoClass = assembly.second->getClass(rawMonoClass); if(monoClass != nullptr) return monoClass; } return nullptr; } void MonoManager::unloadScriptDomain() { if (mScriptDomain != nullptr) { onDomainUnload(); mono_domain_set(mono_get_root_domain(), true); MonoObject* exception = nullptr; mono_domain_try_unload(mScriptDomain, &exception); if (exception != nullptr) MonoUtil::throwIfException(exception); mScriptDomain = nullptr; } for (auto& assemblyEntry : mAssemblies) { assemblyEntry.second->unload(); // "corlib" assembly persists domain unload since it's in the root domain. However we make sure to clear its // class list as it could contain generic instances that use types from other assemblies. if(assemblyEntry.first != "corlib") bs_delete(assemblyEntry.second); // Metas hold references to various assembly objects that were just deleted, so clear them Vector& typeMetas = getScriptMetaData()[assemblyEntry.first]; for (auto& entry : typeMetas) { entry.metaData->scriptClass = nullptr; entry.metaData->thisPtrField = nullptr; } } mAssemblies.clear(); mAssemblies["corlib"] = mCorlibAssembly; } Path MonoManager::getFrameworkAssembliesFolder() const { return Paths::findPath(MONO_VERSION_DATA[(int)MONO_VERSION].path); } Path MonoManager::getMonoEtcFolder() const { return Paths::findPath(MONO_ETC_DIR); } Path MonoManager::getCompilerPath() const { Path compilerPath = Paths::findPath(MONO_COMPILER_DIR); compilerPath.append("mcs.exe"); return compilerPath; } Path MonoManager::getMonoExecPath() const { Path path = Paths::getBinariesPath(); #if BS_PLATFORM == BS_PLATFORM_WIN32 path.append("MonoExec.exe"); #else path.append("MonoExec"); #endif return path; } }