1

The following simple example looks very natural, however it is not getting the right values from the structure. C++ code:

#define E __declspec( dllexport )

struct  XY {
    int x, y;
};

extern "C" {
    E XY* get_xy_ptr() {
        XY p;
        p.x = 15;
        p.y = 23;
        return &p;
    }
}

The C# code to use this function would be:

record struct XY(int x, int y); // C# 10
// OR...
//struct XY
//{
//    public int x;
//    public int y;
//}

[DllImport("CppLibrary.dll")] static extern IntPtr get_xy_ptr();

IntPtr pointer = get_xy_ptr();

XY xy = Marshal.PtrToStructure<XY>(pointer);

Console.WriteLine("xy.x : {0}", xy.x);
Console.WriteLine("xy.y : {0}", xy.y);

However, it does not get the expected results:

C++ Calls Demostration
----------------------
xy_ptr.x : 0 // expected 15 
xy_ptr.y : 0 // expected 23

It is worth clarifying that if the C++ function does not return a pointer, that is, instead of XY*, it returns an explicit XY, and in C# I declare the explicit structure as return, instead of IntPtr, it works perfectly. However, it is not what I am looking for, I need it to be a pointer as it will be used in Emscripten for use in WebAssemply, and it does not accept explicit structures as return.

Thanks for your attention.

Sith2021
  • 3,245
  • 30
  • 22
  • 2
    Your `XY* get_xy_ptr()` returns the address of the local instance `p` but its life-time ends with return from the function. That is Undefined Behavior in C++. – Scheff's Cat Nov 18 '21 at 17:11
  • Thanks Scheff's. I understand your explanation. Is there a way (workaround) to solve it? – Sith2021 Nov 18 '21 at 17:23
  • 1
    My first reflex was to suggest return of `XY`, and according to your sample `struct XY`, this should be fine even with `extern "C"`. This is because the life-time fits and the memory management as well. To return a pointer, you have to manage the life-time elsewhere e.g. by using `new XY`. Now, the life-time is extended (`XY` allocated on heap) but now you have the problem to get rid of this instance after you have used it, i.e. to `delete` it explicitly when it's (really) not anymore needed elsewhere. – Scheff's Cat Nov 18 '21 at 17:26
  • 1
    C++ with a C interface strikes back hard. All the nice memory management which is available in C++ cannot be used with the C interface anymore. (No containers, no smart pointers, etc.) – Scheff's Cat Nov 18 '21 at 17:29
  • Based on the knowledge you gave me; this workaround is an approach (although it is not very Catholic). ```XY _p; extern "C" { E XY* get_xy_ptr() { _p.x = 15; _p.y = 23; return &_p; } }```. - However, since WASM is the final destination, you only have one consumer, and the solution is not that bad. – Sith2021 Nov 18 '21 at 17:46
  • 1
    You could've as well used `static XY p;` in your original version. Please, note that in both approaches there is always only one instance of `XY` (which will be reused for all invocations of `get_xy_ptr()`). So, if the "consumer" doesn't consume the pointee before `get_xy_ptr()` will be called again you will notice "strange interferences". In your sample, it doesn't hurt, as the values will be always the same but the trouble starts as soon as you try to return "something with sense". – Scheff's Cat Nov 18 '21 at 17:55

0 Answers0