2
0

BsMonoManager.cpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "BsMonoManager.h"
  4. #include "Error/BsException.h"
  5. #include "BsScriptMeta.h"
  6. #include "BsMonoAssembly.h"
  7. #include "BsMonoClass.h"
  8. #include "BsMonoUtil.h"
  9. #include "FileSystem/BsFileSystem.h"
  10. #include "Error/BsException.h"
  11. #include "BsApplication.h"
  12. #include "mono/jit/jit.h"
  13. #include <mono/metadata/assembly.h>
  14. #include <mono/metadata/mono-config.h>
  15. #include <mono/metadata/mono-gc.h>
  16. #include <mono/metadata/mono-debug.h>
  17. #include <mono/utils/mono-logger.h>
  18. namespace bs
  19. {
  20. const String MONO_LIB_DIR = "bin/Mono/lib/";
  21. const String MONO_ETC_DIR = "bin/Mono/etc/";
  22. const String MONO_COMPILER_DIR = "bin/Mono/compiler/";
  23. const MonoVersion MONO_VERSION = MonoVersion::v4_5;
  24. struct MonoVersionData
  25. {
  26. String path;
  27. String version;
  28. };
  29. static const MonoVersionData MONO_VERSION_DATA[1] =
  30. {
  31. { MONO_LIB_DIR + "mono/4.5", "v4.0.30319" }
  32. };
  33. void monoLogCallback(const char* logDomain, const char* logLevel, const char* message, mono_bool fatal, void* userData)
  34. {
  35. static const char* monoErrorLevels[] =
  36. {
  37. nullptr,
  38. "error",
  39. "critical",
  40. "warning",
  41. "message",
  42. "info",
  43. "debug"
  44. };
  45. UINT32 errorLevel = 0;
  46. if (logLevel != nullptr)
  47. {
  48. for (UINT32 i = 1; i < 7; i++)
  49. {
  50. if (strcmp(monoErrorLevels[i], logLevel) == 0)
  51. {
  52. errorLevel = i;
  53. break;
  54. }
  55. }
  56. }
  57. if (errorLevel == 0)
  58. {
  59. LOGERR(StringUtil::format("Mono: {0} in domain {1}", message, logDomain));
  60. }
  61. else if (errorLevel <= 2)
  62. {
  63. LOGERR(StringUtil::format("Mono: {0} in domain {1} [{2}]", message, logDomain, logLevel));
  64. }
  65. else if (errorLevel <= 3)
  66. {
  67. LOGWRN(StringUtil::format("Mono: {0} in domain {1} [{2}]", message, logDomain, logLevel));
  68. }
  69. else
  70. {
  71. LOGDBG(StringUtil::format("Mono: {0} in domain {1} [{2}]", message, logDomain, logLevel));
  72. }
  73. }
  74. void monoPrintCallback(const char* string, mono_bool isStdout)
  75. {
  76. LOGWRN(StringUtil::format("Mono error: {0}", string));
  77. }
  78. void monoPrintErrorCallback(const char* string, mono_bool isStdout)
  79. {
  80. LOGERR(StringUtil::format("Mono error: {0}", string));
  81. }
  82. MonoManager::MonoManager()
  83. :mScriptDomain(nullptr), mRootDomain(nullptr), mIsCoreLoaded(false)
  84. {
  85. Path libDir = Paths::findPath(MONO_LIB_DIR);
  86. Path etcDir = getMonoEtcFolder();
  87. Path assembliesDir = getFrameworkAssembliesFolder();
  88. mono_set_dirs(libDir.toString().c_str(), etcDir.toString().c_str());
  89. mono_set_assemblies_path(assembliesDir.toString().c_str());
  90. #if BS_DEBUG_MODE
  91. mono_debug_init(MONO_DEBUG_FORMAT_MONO);
  92. const char* options[] = {
  93. "--soft-breakpoints",
  94. "--debugger-agent=transport=dt_socket,address=127.0.0.1:17615,embedding=1,server=y,suspend=n"
  95. };
  96. mono_jit_parse_options(2, (char**)options);
  97. mono_trace_set_level_string("warning"); // Note: Switch to "debug" for detailed output, disabled for now due to spam
  98. #else
  99. mono_trace_set_level_string("warning");
  100. #endif
  101. mono_trace_set_log_handler(monoLogCallback, this);
  102. mono_trace_set_print_handler(monoPrintCallback);
  103. mono_trace_set_printerr_handler(monoPrintErrorCallback);
  104. mono_config_parse(nullptr);
  105. mRootDomain = mono_jit_init_version("BansheeMono", MONO_VERSION_DATA[(int)MONO_VERSION].version.c_str());
  106. if (mRootDomain == nullptr)
  107. BS_EXCEPT(InternalErrorException, "Cannot initialize Mono runtime.");
  108. }
  109. MonoManager::~MonoManager()
  110. {
  111. for (auto& entry : mAssemblies)
  112. {
  113. bs_delete(entry.second);
  114. }
  115. mAssemblies.clear();
  116. unloadScriptDomain();
  117. if (mRootDomain != nullptr)
  118. {
  119. mono_jit_cleanup(mRootDomain);
  120. mRootDomain = nullptr;
  121. }
  122. // Make sure to explicitly clear this meta-data, as it contains structures allocated from other dynamic libraries,
  123. // which will likely get unloaded right after shutdown
  124. getScriptMetaData().clear();
  125. }
  126. MonoAssembly& MonoManager::loadAssembly(const WString& path, const String& name)
  127. {
  128. MonoAssembly* assembly = nullptr;
  129. if (mScriptDomain == nullptr)
  130. {
  131. String appDomainName = toString(path);
  132. mScriptDomain = mono_domain_create_appdomain(const_cast<char *>(appDomainName.c_str()), nullptr);
  133. mono_domain_set(mScriptDomain, true);
  134. if (mScriptDomain == nullptr)
  135. {
  136. BS_EXCEPT(InternalErrorException, "Cannot create script app domain.");
  137. }
  138. #if BS_DEBUG_MODE
  139. mono_debug_domain_create(mScriptDomain);
  140. #endif
  141. }
  142. auto iterFind = mAssemblies.find(name);
  143. if(iterFind != mAssemblies.end())
  144. {
  145. assembly = iterFind->second;
  146. }
  147. else
  148. {
  149. assembly = new (bs_alloc<MonoAssembly>()) MonoAssembly(path, name);
  150. mAssemblies[name] = assembly;
  151. }
  152. initializeAssembly(*assembly);
  153. return *assembly;
  154. }
  155. void MonoManager::initializeAssembly(MonoAssembly& assembly)
  156. {
  157. if (!assembly.mIsLoaded)
  158. {
  159. assembly.load(mScriptDomain);
  160. // Fully initialize all types that use this assembly
  161. Vector<ScriptMetaInfo>& typeMetas = getScriptMetaData()[assembly.mName];
  162. for (auto& entry : typeMetas)
  163. {
  164. ScriptMeta* meta = entry.metaData;
  165. *meta = entry.localMetaData;
  166. meta->scriptClass = assembly.getClass(meta->ns, meta->name);
  167. if (meta->scriptClass == nullptr)
  168. {
  169. BS_EXCEPT(InvalidParametersException,
  170. "Unable to find class of type: \"" + meta->ns + "::" + meta->name + "\"");
  171. }
  172. if (meta->scriptClass->hasField("mCachedPtr"))
  173. meta->thisPtrField = meta->scriptClass->getField("mCachedPtr");
  174. else
  175. meta->thisPtrField = nullptr;
  176. meta->initCallback();
  177. }
  178. }
  179. if (!mIsCoreLoaded)
  180. {
  181. mIsCoreLoaded = true;
  182. MonoAssembly* corlib = nullptr;
  183. auto iterFind = mAssemblies.find("corlib");
  184. if (iterFind == mAssemblies.end())
  185. {
  186. corlib = new (bs_alloc<MonoAssembly>()) MonoAssembly(L"corlib", "corlib");
  187. mAssemblies["corlib"] = corlib;
  188. }
  189. else
  190. corlib = iterFind->second;
  191. corlib->loadFromImage(mono_get_corlib());
  192. }
  193. }
  194. MonoAssembly* MonoManager::getAssembly(const String& name) const
  195. {
  196. auto iterFind = mAssemblies.find(name);
  197. if(iterFind != mAssemblies.end())
  198. return iterFind->second;
  199. return nullptr;
  200. }
  201. void MonoManager::registerScriptType(ScriptMeta* metaData, const ScriptMeta& localMetaData)
  202. {
  203. Vector<ScriptMetaInfo>& mMetas = getScriptMetaData()[localMetaData.assembly];
  204. mMetas.push_back({ metaData, localMetaData });
  205. }
  206. MonoClass* MonoManager::findClass(const String& ns, const String& typeName)
  207. {
  208. MonoClass* monoClass = nullptr;
  209. for(auto& assembly : mAssemblies)
  210. {
  211. monoClass = assembly.second->getClass(ns, typeName);
  212. if(monoClass != nullptr)
  213. return monoClass;
  214. }
  215. return nullptr;
  216. }
  217. MonoClass* MonoManager::findClass(::MonoClass* rawMonoClass)
  218. {
  219. MonoClass* monoClass = nullptr;
  220. for(auto& assembly : mAssemblies)
  221. {
  222. monoClass = assembly.second->getClass(rawMonoClass);
  223. if(monoClass != nullptr)
  224. return monoClass;
  225. }
  226. return nullptr;
  227. }
  228. void MonoManager::unloadScriptDomain()
  229. {
  230. if (mScriptDomain != nullptr)
  231. {
  232. onDomainUnload();
  233. mono_domain_set(mono_get_root_domain(), true);
  234. mono_domain_finalize(mScriptDomain, 2000);
  235. MonoObject* exception = nullptr;
  236. mono_domain_try_unload(mScriptDomain, &exception);
  237. if (exception != nullptr)
  238. MonoUtil::throwIfException(exception);
  239. mono_gc_collect(mono_gc_max_generation());
  240. mScriptDomain = nullptr;
  241. }
  242. for (auto& assemblyEntry : mAssemblies)
  243. {
  244. assemblyEntry.second->unload();
  245. // Metas hold references to various assembly objects that were just deleted, so clear them
  246. Vector<ScriptMetaInfo>& typeMetas = getScriptMetaData()[assemblyEntry.first];
  247. for (auto& entry : typeMetas)
  248. {
  249. entry.metaData->scriptClass = nullptr;
  250. entry.metaData->thisPtrField = nullptr;
  251. }
  252. }
  253. mAssemblies.clear();
  254. mIsCoreLoaded = false;
  255. }
  256. void MonoManager::loadScriptDomain()
  257. {
  258. if (mScriptDomain != nullptr)
  259. unloadScriptDomain();
  260. if (mScriptDomain == nullptr)
  261. {
  262. char domainName[] = "ScriptDomain";
  263. mScriptDomain = mono_domain_create_appdomain(domainName, nullptr);
  264. mono_domain_set(mScriptDomain, false);
  265. if (mScriptDomain == nullptr)
  266. {
  267. BS_EXCEPT(InternalErrorException, "Cannot create script app domain.");
  268. }
  269. }
  270. if (mScriptDomain != nullptr)
  271. {
  272. for (auto& assemblyEntry : mAssemblies)
  273. initializeAssembly(*assemblyEntry.second);
  274. }
  275. }
  276. Path MonoManager::getFrameworkAssembliesFolder() const
  277. {
  278. return Paths::findPath(MONO_VERSION_DATA[(int)MONO_VERSION].path);
  279. }
  280. Path MonoManager::getMonoEtcFolder() const
  281. {
  282. return Paths::findPath(MONO_ETC_DIR);
  283. }
  284. Path MonoManager::getCompilerPath() const
  285. {
  286. Path compilerPath = Paths::findPath(MONO_COMPILER_DIR);
  287. #if BS_PLATFORM == BS_PLATFORM_WIN32
  288. compilerPath.append("mcs.exe");
  289. #else
  290. compilerPath.append("mcs");
  291. #endif
  292. return compilerPath;
  293. }
  294. }