2
0

BsMonoManager.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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. #include <mono/metadata/threads.h>
  19. namespace bs
  20. {
  21. const String MONO_LIB_DIR = "bin/Mono/lib/";
  22. const String MONO_ETC_DIR = "bin/Mono/etc/";
  23. const String MONO_COMPILER_DIR = "bin/Mono/compiler/";
  24. const MonoVersion MONO_VERSION = MonoVersion::v4_5;
  25. struct MonoVersionData
  26. {
  27. String path;
  28. String version;
  29. };
  30. static const MonoVersionData MONO_VERSION_DATA[1] =
  31. {
  32. { MONO_LIB_DIR + "mono/4.5", "v4.0.30319" }
  33. };
  34. void monoLogCallback(const char* logDomain, const char* logLevel, const char* message, mono_bool fatal, void* userData)
  35. {
  36. static const char* monoErrorLevels[] =
  37. {
  38. nullptr,
  39. "error",
  40. "critical",
  41. "warning",
  42. "message",
  43. "info",
  44. "debug"
  45. };
  46. UINT32 errorLevel = 0;
  47. if (logLevel != nullptr)
  48. {
  49. for (UINT32 i = 1; i < 7; i++)
  50. {
  51. if (strcmp(monoErrorLevels[i], logLevel) == 0)
  52. {
  53. errorLevel = i;
  54. break;
  55. }
  56. }
  57. }
  58. if (errorLevel == 0)
  59. {
  60. LOGERR(StringUtil::format("Mono: {0} in domain {1}", message, logDomain));
  61. }
  62. else if (errorLevel <= 2)
  63. {
  64. LOGERR(StringUtil::format("Mono: {0} in domain {1} [{2}]", message, logDomain, logLevel));
  65. }
  66. else if (errorLevel <= 3)
  67. {
  68. LOGWRN(StringUtil::format("Mono: {0} in domain {1} [{2}]", message, logDomain, logLevel));
  69. }
  70. else
  71. {
  72. LOGDBG(StringUtil::format("Mono: {0} in domain {1} [{2}]", message, logDomain, logLevel));
  73. }
  74. }
  75. void monoPrintCallback(const char* string, mono_bool isStdout)
  76. {
  77. LOGWRN(StringUtil::format("Mono error: {0}", string));
  78. }
  79. void monoPrintErrorCallback(const char* string, mono_bool isStdout)
  80. {
  81. LOGERR(StringUtil::format("Mono error: {0}", string));
  82. }
  83. MonoManager::MonoManager()
  84. :mScriptDomain(nullptr), mRootDomain(nullptr), mCorlibAssembly(nullptr)
  85. {
  86. Path libDir = Paths::findPath(MONO_LIB_DIR);
  87. Path etcDir = getMonoEtcFolder();
  88. Path assembliesDir = getFrameworkAssembliesFolder();
  89. mono_set_dirs(libDir.toString().c_str(), etcDir.toString().c_str());
  90. mono_set_assemblies_path(assembliesDir.toString().c_str());
  91. #if BS_DEBUG_MODE
  92. // Note: For proper debugging experience make sure to open a console window to display stdout and stderr, as Mono
  93. // uses them for much of its logging.
  94. mono_debug_init(MONO_DEBUG_FORMAT_MONO);
  95. const char* options[] = {
  96. "--soft-breakpoints",
  97. "--debugger-agent=transport=dt_socket,address=127.0.0.1:17615,embedding=1,server=y,suspend=n",
  98. "--debug-domain-unload",
  99. // GC options:
  100. // check-remset-consistency: Makes sure that write barriers are properly issued in native code, and therefore
  101. // all old->new generation references are properly present in the remset. This is easy to mess up in native
  102. // code by performing a simple memory copy without a barrier, so it's important to keep the option on.
  103. // verify-before-collections: Unusure what exactly it does, but it sounds like it could help track down
  104. // things like accessing released/moved objects, or attempting to release handles for an unloaded domain.
  105. // xdomain-checks: Makes sure that no references are left when a domain is unloaded.
  106. "--gc-debug=check-remset-consistency,verify-before-collections,xdomain-checks"
  107. };
  108. mono_jit_parse_options(4, (char**)options);
  109. mono_trace_set_level_string("warning"); // Note: Switch to "debug" for detailed output, disabled for now due to spam
  110. #else
  111. mono_trace_set_level_string("warning");
  112. #endif
  113. mono_trace_set_log_handler(monoLogCallback, this);
  114. mono_trace_set_print_handler(monoPrintCallback);
  115. mono_trace_set_printerr_handler(monoPrintErrorCallback);
  116. mono_config_parse(nullptr);
  117. mRootDomain = mono_jit_init_version("BansheeMono", MONO_VERSION_DATA[(int)MONO_VERSION].version.c_str());
  118. if (mRootDomain == nullptr)
  119. BS_EXCEPT(InternalErrorException, "Cannot initialize Mono runtime.");
  120. mono_thread_set_main(mono_thread_current());
  121. // Load corlib
  122. mCorlibAssembly = new (bs_alloc<MonoAssembly>()) MonoAssembly(L"corlib", "corlib");
  123. mCorlibAssembly->loadFromImage(mono_get_corlib());
  124. mAssemblies["corlib"] = mCorlibAssembly;
  125. }
  126. MonoManager::~MonoManager()
  127. {
  128. for (auto& entry : mAssemblies)
  129. {
  130. bs_delete(entry.second);
  131. }
  132. mAssemblies.clear();
  133. unloadScriptDomain();
  134. if (mRootDomain != nullptr)
  135. {
  136. mono_jit_cleanup(mRootDomain);
  137. mRootDomain = nullptr;
  138. }
  139. // Make sure to explicitly clear this meta-data, as it contains structures allocated from other dynamic libraries,
  140. // which will likely get unloaded right after shutdown
  141. getScriptMetaData().clear();
  142. }
  143. MonoAssembly& MonoManager::loadAssembly(const WString& path, const String& name)
  144. {
  145. MonoAssembly* assembly = nullptr;
  146. if (mScriptDomain == nullptr)
  147. {
  148. String appDomainName = "ScriptDomain";
  149. mScriptDomain = mono_domain_create_appdomain(const_cast<char *>(appDomainName.c_str()), nullptr);
  150. if (mScriptDomain == nullptr)
  151. BS_EXCEPT(InternalErrorException, "Cannot create script app domain.");
  152. if(!mono_domain_set(mScriptDomain, true))
  153. BS_EXCEPT(InternalErrorException, "Cannot set script app domain.");
  154. }
  155. auto iterFind = mAssemblies.find(name);
  156. if(iterFind != mAssemblies.end())
  157. {
  158. assembly = iterFind->second;
  159. }
  160. else
  161. {
  162. assembly = new (bs_alloc<MonoAssembly>()) MonoAssembly(path, name);
  163. mAssemblies[name] = assembly;
  164. }
  165. initializeAssembly(*assembly);
  166. return *assembly;
  167. }
  168. void MonoManager::initializeAssembly(MonoAssembly& assembly)
  169. {
  170. if (!assembly.mIsLoaded)
  171. {
  172. assembly.load();
  173. // Fully initialize all types that use this assembly
  174. Vector<ScriptMetaInfo>& typeMetas = getScriptMetaData()[assembly.mName];
  175. for (auto& entry : typeMetas)
  176. {
  177. ScriptMeta* meta = entry.metaData;
  178. *meta = entry.localMetaData;
  179. meta->scriptClass = assembly.getClass(meta->ns, meta->name);
  180. if (meta->scriptClass == nullptr)
  181. {
  182. BS_EXCEPT(InvalidParametersException,
  183. "Unable to find class of type: \"" + meta->ns + "::" + meta->name + "\"");
  184. }
  185. if (meta->scriptClass->hasField("mCachedPtr"))
  186. meta->thisPtrField = meta->scriptClass->getField("mCachedPtr");
  187. else
  188. meta->thisPtrField = nullptr;
  189. meta->initCallback();
  190. }
  191. }
  192. }
  193. MonoAssembly* MonoManager::getAssembly(const String& name) const
  194. {
  195. auto iterFind = mAssemblies.find(name);
  196. if(iterFind != mAssemblies.end())
  197. return iterFind->second;
  198. return nullptr;
  199. }
  200. void MonoManager::registerScriptType(ScriptMeta* metaData, const ScriptMeta& localMetaData)
  201. {
  202. Vector<ScriptMetaInfo>& mMetas = getScriptMetaData()[localMetaData.assembly];
  203. mMetas.push_back({ metaData, localMetaData });
  204. }
  205. MonoClass* MonoManager::findClass(const String& ns, const String& typeName)
  206. {
  207. MonoClass* monoClass = nullptr;
  208. for(auto& assembly : mAssemblies)
  209. {
  210. monoClass = assembly.second->getClass(ns, typeName);
  211. if(monoClass != nullptr)
  212. return monoClass;
  213. }
  214. return nullptr;
  215. }
  216. MonoClass* MonoManager::findClass(::MonoClass* rawMonoClass)
  217. {
  218. MonoClass* monoClass = nullptr;
  219. for(auto& assembly : mAssemblies)
  220. {
  221. monoClass = assembly.second->getClass(rawMonoClass);
  222. if(monoClass != nullptr)
  223. return monoClass;
  224. }
  225. return nullptr;
  226. }
  227. void MonoManager::unloadScriptDomain()
  228. {
  229. if (mScriptDomain != nullptr)
  230. {
  231. onDomainUnload();
  232. mono_domain_set(mono_get_root_domain(), true);
  233. MonoObject* exception = nullptr;
  234. mono_domain_try_unload(mScriptDomain, &exception);
  235. if (exception != nullptr)
  236. MonoUtil::throwIfException(exception);
  237. mScriptDomain = nullptr;
  238. }
  239. for (auto& assemblyEntry : mAssemblies)
  240. {
  241. assemblyEntry.second->unload();
  242. // "corlib" assembly persists domain unload since it's in the root domain. However we make sure to clear its
  243. // class list as it could contain generic instances that use types from other assemblies.
  244. if(assemblyEntry.first != "corlib")
  245. bs_delete(assemblyEntry.second);
  246. // Metas hold references to various assembly objects that were just deleted, so clear them
  247. Vector<ScriptMetaInfo>& typeMetas = getScriptMetaData()[assemblyEntry.first];
  248. for (auto& entry : typeMetas)
  249. {
  250. entry.metaData->scriptClass = nullptr;
  251. entry.metaData->thisPtrField = nullptr;
  252. }
  253. }
  254. mAssemblies.clear();
  255. mAssemblies["corlib"] = mCorlibAssembly;
  256. }
  257. Path MonoManager::getFrameworkAssembliesFolder() const
  258. {
  259. return Paths::findPath(MONO_VERSION_DATA[(int)MONO_VERSION].path);
  260. }
  261. Path MonoManager::getMonoEtcFolder() const
  262. {
  263. return Paths::findPath(MONO_ETC_DIR);
  264. }
  265. Path MonoManager::getCompilerPath() const
  266. {
  267. Path compilerPath = Paths::findPath(MONO_COMPILER_DIR);
  268. compilerPath.append("mcs.exe");
  269. return compilerPath;
  270. }
  271. Path MonoManager::getMonoExecPath() const
  272. {
  273. Path path = Paths::getBinariesPath();
  274. #if BS_PLATFORM == BS_PLATFORM_WIN32
  275. path.append("MonoExec.exe");
  276. #else
  277. path.append("MonoExec");
  278. #endif
  279. return path;
  280. }
  281. }