'C# / C++ Interop: How to modify class in C++ memory from C#

I'm including a C# DLL in my C++ project, and I want both to be able to edit the same struct or class in memory easily I could use functions to set/get the members or struct, but I think this would have a lot more overhead and be cumbersome. I'd rather be able to edit the member variables directly, mapping the memory of the C# side to match the C++ side.

I'm using Mono to manage the C# from C++ but I'm not sure this is important.

This is my attempt at doing so, but any changes in C# only affect the C# struct. How can I fix this?

C++ (Unmanaged) Code:

#ifdef ENGINE_CORE
    #define ENGINE_CORE_API __declspec(dllexport)
#else
    #define ENGINE_CORE_API __declspec(dllimport)
#endif

struct TestStruct {
    int myElement = 4;
};

TestStruct testStruct;

extern "C" {
    ENGINE_CORE_API void GetTestStruct(TestStruct** transf) {
        *transf = &testStruct;
    }
}

C# (Managed) Code:

[StructLayout(LayoutKind.Sequential)]
class TestStruct {
    public int myElement;
};

public class Example {
    // Called in a loop:
    public void OnUpdate() {
        GetTestStruct(out TestStruct testStruct);
        testStruct.myElement += 2;
        Logger.Print($"Int: {testStruct.myElement}");
    }

    #region DllImports
    [DllImport("EngineCore")]
    static extern void GetTestStruct([Out] out TestStruct comp);
    #endregion
}

Output:

[2022-02-26 00:02:54.959] [Debug Logger] [info] Int: 6
[2022-02-26 00:02:55.220] [Debug Logger] [info] Int: 6
[2022-02-26 00:02:55.234] [Debug Logger] [info] Int: 6

Expected Output:

[2022-02-26 00:02:54.959] [Debug Logger] [info] Int: 6
[2022-02-26 00:02:55.220] [Debug Logger] [info] Int: 8
[2022-02-26 00:02:55.234] [Debug Logger] [info] Int: 10


Solution 1:[1]

From C# side, you can bring all the relative memory address offsets of all fields of object instance into C++ and access them by using the offsets added to their object adresses and cast to their POD types.

Then your C++ class would be made of only pointers directly pointing to the C# object fields.

struct TestStruct {

    // points to relevant field
    int* myElement;
};

TestStruct t({ptr_from_csharp});
*t.myElement=5; // pinned C# object's field changes

If you need the opposite, then C++-side doesn't require the extra pinning operation since it doesn't have GC.

If you don't want to fiddle with pointer type fields, then you can overload getter/setter of fields on C# and run the C++ DLL in those getter/setter methods so that it looks like C# owns the memory from outside but actually only changes the C++-space memory.

class TestStruct {
    public int myElement {
        get { return cpp("C++");} 
        set { cpp("change C++");}
    }
};

But this would bring interop overhead.

Solution 2:[2]

I would write some kind of awk or perl script that reads the C# class and spits out the required C++ struct.

To this end, it would be helpful to:

  • annotate or bracket the C# class(es) with comments that the script recognises and uses to identify said class(es)

  • move the C++ struct(s) to a separate header file

Then, when a C# class changes, just run the script on the relevant file(s) and recompile your C++ code.

Don't forget to have the script write out a comment the the effect that the output file is machine-generated so that you don't inadvertently hand-edit it.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Paul Sanders