| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
- //**************** Copyright (c) 2016 Marko Pintera ([email protected]). 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 <mono/metadata/assembly.h>
- #include <mono/metadata/mono-config.h>
- #include <mono/metadata/mono-gc.h>
- #include <mono/metadata/mono-debug.h>
- #include <mono/utils/mono-logger.h>
- #include <mono/metadata/threads.h>
- 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>()) 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<char *>(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>()) 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<ScriptMetaInfo>& 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<ScriptMetaInfo>& 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<ScriptMetaInfo>& 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;
- }
- }
|