Godot: Implement Mono/C# project exporting

Created on 12 Jan 2018  路  21Comments  路  Source: godotengine/godot

Known issue, but it seems we have no issue yet to track it, if I haven't missed something.

bug mono

Most helpful comment

@KellyThomas Good point. I'll actually close this one for now, exporting for other platforms should probably be either milestones in a rodmap somewhere or new, seperate issues.

Here's to being able to export C# projects! Thanks for the great work, neikeq. :)

All 21 comments

Thanks, I was wondering if there was an issue for it too, but never found one either :)

Sadly it's too late for 3.0 now 馃樋

So 3.0 will be released with Mono export support, which will be added in a later maintenance release (likely 3.0.1, and of course 3.1 too), so C# users won't be able to deploy to production with 3.0.
3.0.1 should arrive faster than it takes to make a production-ready game though, so it's not too bad..

Yeah, but will make some things like doing Game Jams with Godot 3.0 in C# hard. Let's hope for a fast 3.0.1 arrival :)

_Copy/paste from #15972 with some updates._


mono_set_dirs works fine with local mscorlib.dll and assemblies.

Tested on:

  • macOS 10.13.2 (17C205),
  • Debian GNU/Linux buster/sid (4.14.0-3-amd64),
  • Windows 10 (1709 build 16299.192)

Mono 5.4.1.7 (2017-06/e66d9abbb27), with DLLs from macOS Mono installation on all platforms.

Editor runs with mscorlib.dll alone, precompiled Mono "Hello world" project runs with mscorlib.dll and some of System*.dll (see "File tree" below).


File tree

mono_lib



Patch (Updated for Windows version)

diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 6c07c90f7..e27cd03d8 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -155,25 +155,7 @@ void GDMono::initialize() {
    mono_trace_set_printerr_handler(gdmono_MonoPrintCallback);
 #endif

-#ifdef WINDOWS_ENABLED
-   mono_reg_info = MonoRegUtils::find_mono();
-
-   CharString assembly_dir;
-   CharString config_dir;
-
-   if (mono_reg_info.assembly_dir.length() && DirAccess::exists(mono_reg_info.assembly_dir)) {
-       assembly_dir = mono_reg_info.assembly_dir.utf8();
-   }
-
-   if (mono_reg_info.config_dir.length() && DirAccess::exists(mono_reg_info.config_dir)) {
-       config_dir = mono_reg_info.config_dir.utf8();
-   }
-
-   mono_set_dirs(assembly_dir.length() ? assembly_dir.get_data() : NULL,
-           config_dir.length() ? config_dir.get_data() : NULL);
-#else
-   mono_set_dirs(NULL, NULL);
-#endif
+   mono_set_dirs(OS::get_singleton()->get_executable_path().get_base_dir().utf8().get_data(), OS::get_singleton()->get_executable_path().get_base_dir().utf8().get_data());

    GDMonoAssembly::initialize();


Mono embedding documentation:
http://www.mono-project.com/docs/advanced/embedding/#configuring-the-runtime

For some reason static Mono support is disabled on Windows (in modules/mono/config.py). Mono distribution for Windows provides static library: libmono-static-sgen.lib. Why?

The ideal would be to export mscorlib in the PCK file, but if it proves not to be possible (it seems mscorlib is hard-coded to be loaded from the assemblies path), then that will be the only option.

For some reason static Mono support is disabled on Windows (in modules/mono/config.py). Mono distribution for Windows provides static library: libmono-static-sgen.lib. Why?

You can't link mono statically on Windows.

There is preload hook in mscorlib loading code assembly.c#L3479 and it is triggering GDMonoAssembly::_preload_hook gd_mono_assembly.cpp#L94.

I think whatever is the properly documented and most straightforward way to do it makes the most sense.

The wording isn't quite clear, but it looks like Apple might need to code sign the assemblies, which wouldn't be possible with a pck file, but I may be reading that wrong. Read the third paragraph in the section named "App code signing".
https://www.apple.com/business/docs/iOS_Security_Guide.pdf?platform=hootsuite

If not including it in the pack file poses a problem down the road it could always be changed in the future.

My opinion is to just leave it as a folder of assemblies for now.

I'm not sure if it's relevant, but while Unity packs up resources into packed files it doesn't pack up the DLLs with the rest of the assets either. The assemblies are loosely stored in their own directory. I'm going to probably take a look and see how other game engines do this as well. I'm going to make a wild guess that they all do it this same way :)

IIRC Apple disallow loading any code at runtime, Xamarin.iOS precompile all assemblies into static native libraries, with tons of limitations.

Mono supports AOT compilation of assemblies. Xamarin.iOS uses that afaik, but of course testing will need to be done, and I don't know what kind of limitations there are with said feature.

Here's POC for loading mscorlib.dll and System*.dll form memory buffer inside preload hook (buffer is filled from hardcoded files), and it's probably overcomplicated but working.

Better and more compact variant for loading mscorlib.dll and all assemblies form res://

diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp
index ba56ed6..caa5799 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.cpp
+++ b/modules/mono/mono_gd/gd_mono_assembly.cpp
@@ -96,6 +96,7 @@ MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **asse
    (void)user_data; // UNUSED

    if (search_dirs.empty()) {
+       search_dirs.push_back("res://");
 #ifdef TOOLS_DOMAIN
        search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir());
 #endif
@@ -116,14 +117,38 @@ MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **asse
        }
    }

+   String name = mono_assembly_name_get_name(aname);
+
+   bool has_extension = name.ends_with(".dll");
+   name = has_extension ? name.get_basename() : name;
+   if (name == "mscorlib") {
+       if (FileAccess::exists("res://mscorlib.dll")) {
+           OS::get_singleton()->print(String("Mono: Preloading buildin \"mscorlib\"\n").utf8());
+           MonoAssembly *assembly = _load_assembly_from("mscorlib", "res://mscorlib.dll");
+           if (assembly != NULL) {
+               return assembly;
+           }
+       } else {
+           OS::get_singleton()->print(String("Mono: No buildin \"mscorlib\" found\n").utf8());
+       }
+   }
+
    return NULL;
 }

 MonoAssembly *GDMonoAssembly::_load_assembly_from(const String &p_name, const String &p_path) {

-   GDMonoAssembly *assembly = memnew(GDMonoAssembly(p_name, p_path));
-
    MonoDomain *domain = mono_domain_get();
+   GDMonoAssembly **stored_assembly = GDMono::get_singleton()->get_loaded_assembly(p_name);
+
+   if (stored_assembly != NULL) {
+       if (OS::get_singleton()->is_stdout_verbose())
+           OS::get_singleton()->print(String("Mono: Assembly " + p_name + " already loaded\n").utf8());
+
+       return (*stored_assembly)->get_assembly();
+   }
+
+   GDMonoAssembly *assembly = memnew(GDMonoAssembly(p_name, p_path));

    Error err = assembly->load(domain);

@bruvzg You should make a PR of your work :)

Unity has IL2CPP -> emscripten, what's the strategy for getting C# scripts working on the web?

There are some IL > LLVM > WASM experiments (Mono and WebAssembly), but it's only proof-of-concept and not ready for production.

Has there been any progression with exporting a project _with_ a C# assembly? For example, if I have a project with some GDscripts and C# scripts, along with an assembly, I can't export it properly. When I try to run the game, it gives an error about the C# script.

@Ertain The logic is already there on the export plugin side, but the two missing points I mention in #16920 are holding it for now. Sadly, @reduz was pretty busy preparing for GDC, so we couldn't talk this yet.

However, if you are willing to do some manual work, you can already export your game (Export PCK-only is not yet working though). You only need to replace the exported binary with one you build yourself with the mono module enabled after exporting.

@neikeq Do you mean exporting in a zip archive? I've tried that; it doesn't work. When I export a simple project (that consists of a GDscript, a C# script, and a *.DLL assembly), then put in an executable in the directory (an executable that already has Mono), and try to run it, the program can't find the assembly, and it gives an error.

~Edit: for completeness, here's the sample project to which I was referring. Note that this was written on Linux Mint, so it may be difficult to import.~

@neikeq I also tried placing the "GodotSharpTools.dll" and "libmonosgen-2.0.so" files in the exported directory. But that didn't work, either.

@mhilbrunner Now that we have exports for desktop platforms it might be useful to update the description for this issue.

@KellyThomas Good point. I'll actually close this one for now, exporting for other platforms should probably be either milestones in a rodmap somewhere or new, seperate issues.

Here's to being able to export C# projects! Thanks for the great work, neikeq. :)

Was this page helpful?
0 / 5 - 0 ratings