0

I am relatively new to C++, and have copied some code from a live stream to get started with embedding C# mono into my game engine.

I have created a C# script:

using System;

namespace Neutron {
    public class Main {
        public float FloatVar { get; set; }
    
        public Main() {
            Console.WriteLine("Main constructor");
        }

        public void PrintMessage() {
            Console.WriteLine("Hello World from C#!");
        }

        public void PrintCustomMessage(string message) {
            Console.WriteLine($"C# says: {message}");
        }
    }   
}

and built it to ../Sandbox/Sandbox/bin/Debug/Sandbox.dll (which i have verified within the c++ program that I am embedding it into)

when i call mono_runtime_object_init passing the MonoObject* created from calling mono_object_new (I have verified that it doesnt return a nullptr)

The following error occurs: * Assertion at object.c:116, condition `is_ok (error)' not met, function:mono_runtime_object_init, (null) assembly:/usr/lib/mono/4.5/mscorlib.dll type:TypeInitializationException member:(null) Followed by a long stack trace.

Here are my relavent files:

  • ScriptingEngine.cpp (See the InitMono function towards the bottom)
//
// Created by aw1lt on 05/12/22.
//


#include "ScriptingEngine.h"
#include "Logger.h"

#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
#include <fstream>

namespace Neutron {

    struct ScriptEngineData {
        MonoDomain* RootDomain = nullptr;
        MonoDomain* AppDomain = nullptr;
        MonoAssembly* CoreAssembly = nullptr;
    };

    char* ReadBytes(const std::string& filepath, uint32_t* outSize) {
        std::ifstream stream(filepath, std::ios::binary | std::ios::ate);

        if (!stream) {
            // Failed to open the file
            return nullptr;
        }

        std::streampos end = stream.tellg();
        stream.seekg(0, std::ios::beg);
        uint32_t size = end - stream.tellg();

        if (size == 0) {
            // File is empty
            return nullptr;
        }

        char* buffer = new char[size];
        stream.read((char*)buffer, size);
        stream.close();

        *outSize = size;
        return buffer;
    }

    MonoAssembly* LoadCSharpAssembly(const std::string& assemblyPath) {
        uint32_t fileSize = 0;
        char* fileData = ReadBytes(assemblyPath, &fileSize);

        // NOTE: We can't use this image for anything other than loading the assembly because this image doesn't have a reference to the assembly
        MonoImageOpenStatus status;
        MonoImage* image = mono_image_open_from_data_full(fileData, fileSize, 1, &status, 0);

        if (status != MONO_IMAGE_OK) {
            const char* errorMessage = mono_image_strerror(status);
            Logger::Crit(errorMessage);
            return nullptr;
        }

        MonoAssembly* assembly = mono_assembly_load_from_full(image, assemblyPath.c_str(), &status, 0);
        mono_image_close(image);

        // Don't forget to free the file data
        delete[] fileData;

        return assembly;
    }

    void PrintAssemblyTypes(MonoAssembly* assembly) {
        MonoImage* image = mono_assembly_get_image(assembly);
        const MonoTableInfo* typeDefinitionsTable = mono_image_get_table_info(image, MONO_TABLE_TYPEDEF);
        int32_t numTypes = mono_table_info_get_rows(typeDefinitionsTable);

        for (int32_t i = 0; i < numTypes; i++) {
            uint32_t cols[MONO_TYPEDEF_SIZE];
            mono_metadata_decode_row(typeDefinitionsTable, i, cols, MONO_TYPEDEF_SIZE);

            const char* nameSpace = mono_metadata_string_heap(image, cols[MONO_TYPEDEF_NAMESPACE]);
            const char* name = mono_metadata_string_heap(image, cols[MONO_TYPEDEF_NAME]);

            Logger::Log(std::string(nameSpace) + "." + name);
        }
    }


    static ScriptEngineData* s_Data;

    void ScriptingEngine::Init() {
        s_Data = new ScriptEngineData();

        InitMono();
    }

    void ScriptingEngine::Shutdown() {
        delete s_Data;
    }

    void ScriptingEngine::InitMono(std::string path) {
        MonoDomain* rootDomain = mono_jit_init("NeutronJITRuntime");
        Logger::Assert(rootDomain != nullptr);

        //system(("cat " + path).c_str());

        // Store the root domain pointer
        s_Data->RootDomain = rootDomain;

        // Create an App Domain
        s_Data->AppDomain = mono_domain_create_appdomain("NeutronScriptRuntime", nullptr);
        mono_domain_set(s_Data->AppDomain, true);

        s_Data->CoreAssembly = LoadCSharpAssembly(path);

        Logger::Assert(s_Data->CoreAssembly != nullptr);

        MonoImage* assemblyImage = mono_assembly_get_image(s_Data->CoreAssembly);
        MonoClass* monoClass = mono_class_from_name(assemblyImage, "Neutron", "Main");

        Logger::Assert(monoClass != nullptr);

        PrintAssemblyTypes(s_Data->CoreAssembly);

        MonoObject* instance = mono_object_new(s_Data->AppDomain, monoClass);
        Logger::Assert(instance != nullptr);

        
        mono_runtime_object_init(instance); //  << ERROR HAPPENS HERE

    }

    void ScriptingEngine::ShutdownMono() {

    }
} // Neutron
  • my header file, ScriptingEngine.h
//
// Created by aw1lt on 05/12/22.
//

#ifndef NEUTRONENGINE_SCRIPTINGENGINE_H
#define NEUTRONENGINE_SCRIPTINGENGINE_H

#include <string>

namespace Neutron {

    class ScriptingEngine {
    public:
        static void Init();
        static void Shutdown();
    private:
        static void InitMono(std::string path = "../Sandbox/Sandbox/bin/Debug/Sandbox.dll");
        static void ShutdownMono();
    };

} // Neutron

#endif //NEUTRONENGINE_SCRIPTINGENGINE_H

I also tried the answers on this page.

Thank you, and sorry if this answer is a mess.

aw1lt
  • 24
  • 1
  • 4
  • https://blog.unity.com/technology/unity-and-net-whats-next If Unity is moving to .NET CoreCLR, you probably should do the same instead of relying on Mono (in maintenance mode now, and is probably going away in 2023/2024). – Lex Li Dec 06 '22 at 03:44

0 Answers0