summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/mono/editor/bindings_generator.cpp15
-rw-r--r--modules/mono/editor/godotsharp_export.cpp79
-rw-r--r--modules/mono/managed_callable.cpp1
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp31
-rw-r--r--modules/mono/mono_gd/gd_mono.h1
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.cpp71
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.h6
-rw-r--r--modules/mono/mono_gd/gd_mono_log.cpp2
-rw-r--r--modules/mono/utils/macros.h2
9 files changed, 136 insertions, 72 deletions
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index bdf9cf965f..258b8ed3ed 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -1664,6 +1664,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
}
if (!p_imethod.is_internal) {
+ // TODO: This alone adds ~0.2 MB of bloat to the core API assembly. It would be
+ // better to generate a table in the C++ glue instead. That way the strings wouldn't
+ // add that much extra bloat as they're already used in engine code. Also, it would
+ // probably be much faster than looking up the attributes when fetching methods.
p_output.append(MEMBER_BEGIN "[GodotMethod(\"");
p_output.append(p_imethod.name);
p_output.append("\")]");
@@ -2139,7 +2143,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
if (return_type->ret_as_byref_arg) {
p_output.append("\tif (" CS_PARAM_INSTANCE " == nullptr) { *arg_ret = ");
p_output.append(fail_ret);
- p_output.append("; ERR_FAIL_MSG(\"Parameter ' arg_ret ' is null.\"); }\n");
+ p_output.append("; ERR_FAIL_MSG(\"Parameter ' " CS_PARAM_INSTANCE " ' is null.\"); }\n");
} else {
p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE ", ");
p_output.append(fail_ret);
@@ -2390,6 +2394,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY)
continue;
+ if (property.name.find("/") >= 0) {
+ // Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector.
+ continue;
+ }
+
PropertyInterface iprop;
iprop.cname = property.name;
iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname);
@@ -2402,7 +2411,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
bool valid = false;
iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid);
- ERR_FAIL_COND_V(!valid, false);
+ ERR_FAIL_COND_V_MSG(!valid, false, "Invalid property: '" + itype.name + "." + String(iprop.cname) + "'.");
iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));
@@ -2414,8 +2423,6 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
iprop.proxy_name += "_";
}
- iprop.proxy_name = iprop.proxy_name.replace("/", "__"); // Some members have a slash...
-
iprop.prop_doc = nullptr;
for (int i = 0; i < itype.class_doc->properties.size(); i++) {
diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp
index 4126da16be..d6a271f1d9 100644
--- a/modules/mono/editor/godotsharp_export.cpp
+++ b/modules/mono/editor/godotsharp_export.cpp
@@ -32,68 +32,70 @@
#include <mono/metadata/image.h>
+#include "core/io/file_access_pack.h"
#include "core/os/os.h"
+#include "core/project_settings.h"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_assembly.h"
#include "../mono_gd/gd_mono_cache.h"
+#include "../utils/macros.h"
namespace GodotSharpExport {
-String get_assemblyref_name(MonoImage *p_image, int index) {
+struct AssemblyRefInfo {
+ String name;
+ uint16_t major;
+ uint16_t minor;
+ uint16_t build;
+ uint16_t revision;
+};
+
+AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) {
const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
uint32_t cols[MONO_ASSEMBLYREF_SIZE];
mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE);
- return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
+ return {
+ String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])),
+ (uint16_t)cols[MONO_ASSEMBLYREF_MAJOR_VERSION],
+ (uint16_t)cols[MONO_ASSEMBLYREF_MINOR_VERSION],
+ (uint16_t)cols[MONO_ASSEMBLYREF_BUILD_NUMBER],
+ (uint16_t)cols[MONO_ASSEMBLYREF_REV_NUMBER]
+ };
}
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
- String ref_name = get_assemblyref_name(image, i);
+ AssemblyRefInfo ref_info = get_assemblyref_name(image, i);
+
+ const String &ref_name = ref_info.name;
if (r_assembly_dependencies.has(ref_name))
continue;
- GDMonoAssembly *ref_assembly = nullptr;
- String path;
- bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe");
-
- for (int j = 0; j < p_search_dirs.size(); j++) {
- const String &search_dir = p_search_dirs[j];
-
- if (has_extension) {
- path = search_dir.plus_file(ref_name);
- if (FileAccess::exists(path)) {
- GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true);
- if (ref_assembly != nullptr)
- break;
- }
- } else {
- path = search_dir.plus_file(ref_name + ".dll");
- if (FileAccess::exists(path)) {
- GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
- if (ref_assembly != nullptr)
- break;
- }
-
- path = search_dir.plus_file(ref_name + ".exe");
- if (FileAccess::exists(path)) {
- GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
- if (ref_assembly != nullptr)
- break;
- }
- }
- }
+ GDMonoAssembly *ref_assembly = NULL;
+
+ {
+ MonoAssemblyName *ref_aname = mono_assembly_name_new("A"); // We can't allocate an empty MonoAssemblyName, hence "A"
+ CRASH_COND(ref_aname == nullptr);
+ SCOPE_EXIT {
+ mono_assembly_name_free(ref_aname);
+ mono_free(ref_aname);
+ };
- ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
+ mono_assembly_get_assemblyref(image, i, ref_aname);
- // Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir.
- r_assembly_dependencies[ref_name] = path;
+ if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) {
+ ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
+ }
+
+ r_assembly_dependencies[ref_name] = ref_assembly->get_path();
+ }
Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_assembly_dependencies);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'.");
@@ -113,6 +115,11 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
+ if (p_custom_bcl_dir.length()) {
+ // Only one mscorlib can be loaded. We need this workaround to make sure we get it from the right BCL directory.
+ r_assembly_dependencies["mscorlib"] = p_custom_bcl_dir.plus_file("mscorlib.dll").simplify_path();
+ }
+
for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) {
String assembly_name = *key;
String assembly_path = p_initial_assemblies[*key];
diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp
index dfd78a8244..26347e9162 100644
--- a/modules/mono/managed_callable.cpp
+++ b/modules/mono/managed_callable.cpp
@@ -82,6 +82,7 @@ CallableCustom::CompareLessFunc ManagedCallable::get_compare_less_func() const {
}
ObjectID ManagedCallable::get_object() const {
+ // TODO: If the delegate target extends Godot.Object, use that instead!
return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id();
}
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 3298c5da4c..a6ed2957e6 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -515,8 +515,8 @@ void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) {
GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) {
- if (p_name == "mscorlib")
- return get_corlib_assembly();
+ if (p_name == "mscorlib" && corlib_assembly)
+ return corlib_assembly;
MonoDomain *domain = mono_domain_get();
uint32_t domain_id = domain ? mono_domain_get_id(domain) : 0;
@@ -526,7 +526,9 @@ GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) {
bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) {
+#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
+#endif
MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
bool result = load_assembly(p_name, aname, r_assembly, p_refonly);
@@ -538,26 +540,27 @@ bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bo
bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) {
+#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
+#endif
- print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");
-
- MonoImageOpenStatus status = MONO_IMAGE_OK;
- MonoAssembly *assembly = mono_assembly_load_full(p_aname, nullptr, &status, p_refonly);
+ return load_assembly(p_name, p_aname, r_assembly, p_refonly, GDMonoAssembly::get_default_search_dirs());
+}
- if (!assembly)
- return false;
+bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs) {
- ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false);
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!r_assembly);
+#endif
- uint32_t domain_id = mono_domain_get_id(mono_domain_get());
+ print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");
- GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);
+ GDMonoAssembly *assembly = GDMonoAssembly::load(p_name, p_aname, p_refonly, p_search_dirs);
- ERR_FAIL_COND_V(stored_assembly == nullptr, false);
- ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false);
+ if (!assembly)
+ return false;
- *r_assembly = *stored_assembly;
+ *r_assembly = assembly;
print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path());
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 4898833e8e..153ae891f7 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -241,6 +241,7 @@ public:
bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false);
bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false);
+ bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs);
bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false);
Error finalize_and_unload_domain(MonoDomain *p_domain);
diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp
index 0f211eebc6..da2ed2a7fc 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.cpp
+++ b/modules/mono/mono_gd/gd_mono_assembly.cpp
@@ -33,6 +33,7 @@
#include <mono/metadata/mono-debug.h>
#include <mono/metadata/tokentype.h>
+#include "core/io/file_access_pack.h"
#include "core/list.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
@@ -99,7 +100,7 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin
// - The 'load' hook is called after the assembly has been loaded. Its job is to add the
// assembly to the list of loaded assemblies so that the 'search' hook can look it up.
-void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) {
+void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, [[maybe_unused]] void *user_data) {
String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));
@@ -133,9 +134,7 @@ MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *an
return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true);
}
-MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly) {
-
- (void)user_data; // UNUSED
+MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, [[maybe_unused]] void *user_data, bool refonly) {
String name = String::utf8(mono_assembly_name_get_name(aname));
bool has_extension = name.ends_with(".dll") || name.ends_with(".exe");
@@ -147,15 +146,13 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d
return nullptr;
}
-MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) {
-
- (void)user_data; // UNUSED
+MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) {
String name = String::utf8(mono_assembly_name_get_name(aname));
- return _load_assembly_search(name, search_dirs, refonly);
+ return _load_assembly_search(name, aname, refonly, search_dirs);
}
-MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) {
+MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
MonoAssembly *res = nullptr;
String path;
@@ -168,21 +165,21 @@ MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const
if (has_extension) {
path = search_dir.plus_file(p_name);
if (FileAccess::exists(path)) {
- res = _real_load_assembly_from(path, p_refonly);
+ res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != nullptr)
return res;
}
} else {
path = search_dir.plus_file(p_name + ".dll");
if (FileAccess::exists(path)) {
- res = _real_load_assembly_from(path, p_refonly);
+ res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != nullptr)
return res;
}
path = search_dir.plus_file(p_name + ".exe");
if (FileAccess::exists(path)) {
- res = _real_load_assembly_from(path, p_refonly);
+ res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != nullptr)
return res;
}
@@ -230,7 +227,7 @@ void GDMonoAssembly::initialize() {
mono_install_assembly_load_hook(&assembly_load_hook, nullptr);
}
-MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly) {
+MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) {
Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
ERR_FAIL_COND_V_MSG(data.empty(), nullptr, "Could read the assembly in the specified location");
@@ -255,7 +252,33 @@ MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, boo
true, &status, p_refonly,
image_filename.utf8());
- ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from the loaded data");
+ ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from memory: '" + p_path + "'.");
+
+ if (p_aname != nullptr) {
+ // Check assembly version
+ const MonoTableInfo *table = mono_image_get_table_info(image, MONO_TABLE_ASSEMBLY);
+
+ ERR_FAIL_NULL_V(table, nullptr);
+
+ if (mono_table_info_get_rows(table)) {
+ uint32_t cols[MONO_ASSEMBLY_SIZE];
+ mono_metadata_decode_row(table, 0, cols, MONO_ASSEMBLY_SIZE);
+
+ // Not sure about .NET's policy. We will only ensure major and minor are equal, and ignore build and revision.
+ uint16_t major = cols[MONO_ASSEMBLY_MAJOR_VERSION];
+ uint16_t minor = cols[MONO_ASSEMBLY_MINOR_VERSION];
+
+ uint16_t required_minor;
+ uint16_t required_major = mono_assembly_name_get_version(p_aname, &required_minor, nullptr, nullptr);
+
+ if (required_major != 0) {
+ if (major != required_major && minor != required_minor) {
+ mono_image_close(image);
+ return nullptr;
+ }
+ }
+ }
+ }
#ifdef DEBUG_ENABLED
Vector<uint8_t> pdb_data;
@@ -425,6 +448,26 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class)
return match;
}
+GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
+
+ if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll"))
+ return GDMono::get_singleton()->get_corlib_assembly();
+
+ // We need to manually call the search hook in this case, as it won't be called in the next step
+ MonoAssembly *assembly = mono_assembly_invoke_search_hook(p_aname);
+
+ if (!assembly) {
+ assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs);
+ ERR_FAIL_NULL_V(assembly, nullptr);
+ }
+
+ GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
+ ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?");
+ ERR_FAIL_COND_V(loaded_asm->get_assembly() != assembly, nullptr);
+
+ return loaded_asm;
+}
+
GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) {
if (p_name == "mscorlib" || p_name == "mscorlib.dll")
diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h
index 43c8225b74..7fc1817993 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.h
+++ b/modules/mono/mono_gd/gd_mono_assembly.h
@@ -93,8 +93,8 @@ class GDMonoAssembly {
static MonoAssembly *_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly);
static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly);
- static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly);
- static MonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly);
+ static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname = nullptr);
+ static MonoAssembly *_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs);
friend class GDMono;
static void initialize();
@@ -120,7 +120,9 @@ public:
static String find_assembly(const String &p_name);
static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String());
+ static const Vector<String> &get_default_search_dirs() { return search_dirs; }
+ static GDMonoAssembly *load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs);
static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly);
GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly);
diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp
index ca16c2b76a..b56350ae1b 100644
--- a/modules/mono/mono_gd/gd_mono_log.cpp
+++ b/modules/mono/mono_gd/gd_mono_log.cpp
@@ -175,7 +175,7 @@ void GDMonoLog::initialize() {
log_level_id = get_log_level_id(log_level.get_data());
if (log_file) {
- OS::get_singleton()->print("Mono: Logfile is: %s\n", log_file_path.utf8().get_data());
+ OS::get_singleton()->print("Mono: Log file is: '%s'\n", log_file_path.utf8().get_data());
mono_trace_set_log_handler(mono_log_callback, this);
} else {
OS::get_singleton()->printerr("Mono: No log file, using default log handler\n");
diff --git a/modules/mono/utils/macros.h b/modules/mono/utils/macros.h
index 8650d6cc09..dc542477f5 100644
--- a/modules/mono/utils/macros.h
+++ b/modules/mono/utils/macros.h
@@ -68,6 +68,6 @@ public:
} // namespace gdmono
#define SCOPE_EXIT \
- auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]()
+ auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]() -> void
#endif // UTIL_MACROS_H