Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ref/out value type method params not working correctly #1820

Open
JordanL8 opened this issue Jan 2, 2024 · 6 comments
Open

Ref/out value type method params not working correctly #1820

JordanL8 opened this issue Jan 2, 2024 · 6 comments

Comments

@JordanL8
Copy link

JordanL8 commented Jan 2, 2024

Brief Description

I have an abstract method in C++ that is implemented in C# and called from C++ at runtime. Some parameters are passed by reference and the caller in C++ expects the implementer to modify these param values. It works correctly when the params are either marshaled as classes or are primitives. If one of the params is marshaled as a struct and configured in CppSharp to be passed by ref (parameterUsage set to InOut), the value in the implemented method isn't correctly passed out to C++.

@JordanL8
Copy link
Author

JordanL8 commented Feb 2, 2024

Looked more into this and it looks like the following constructor:

private MyStruct(void* native, bool skipVTables = false) : this()
{
    __instance = *(global::MyStruct.__Internal*) native;
}

Which is called from

__CreateInstance(__IntPtr native, bool skipVTables = false)

Is causing my issue. The struct instance isn't set to the same address as the passed in native pointer. Unsure how to handle this because this seems like the correct behavior for a value type.

Possibly an override for __CreateInstance that uses the native pointer, specifically used for ref/out value type params?

@tritao
Copy link
Collaborator

tritao commented Feb 2, 2024

Looked more into this and it looks like the following constructor:

private MyStruct(void* native, bool skipVTables = false) : this()
{
    __instance = *(global::MyStruct.__Internal*) native;
}

Which is called from

__CreateInstance(__IntPtr native, bool skipVTables = false)

Is causing my issue. The struct instance isn't set to the same address as the passed in native pointer. Unsure how to handle this because this seems like the correct behavior for a value type.

Possibly an override for __CreateInstance that uses the native pointer, specifically used for ref/out value type params?

Can you post MyStruct? Basically just looking to see if its dynamic (as in, as a v-table) and/or inherits anything.

@JordanL8
Copy link
Author

JordanL8 commented Feb 7, 2024

Sure, here's the struct in C++:

struct Position
{
	union
	{
		struct
		{
			double x, y, z;
		};
		double v[3];
	};

	// ... constructor and other methods.
}

I've stripped out all the imported methods from the __Internal struct and removed the passthrough methods for the following generated class to make it more readable. It doesn't inherit from anything and has none of the vtable stuff/the imported methods were just called directly.

Generated Class
public unsafe partial struct Position
{
    [StructLayout(LayoutKind.Explicit, Size = 24)]
    public partial struct __Internal
    {
        [FieldOffset(0)]
        internal double x;

        [FieldOffset(8)]
        internal double y;

        [FieldOffset(16)]
        internal double z;

        [FieldOffset(0)]
        internal fixed double v[3];
    }

    private Position.__Internal __instance;
    internal Position.__Internal __Instance => __instance;

    internal static Position __CreateInstance(__IntPtr native, bool skipVTables = false)
    {
        return new Position(native.ToPointer(), skipVTables);
    }

    internal static Position __CreateInstance(__Internal native, bool skipVTables = false)
    {
        return new Position(native, skipVTables);
    }

    private Position(__Internal native, bool skipVTables = false)
        : this()
    {
        __instance = native;
    }

    private Position(void* native, bool skipVTables = false) : this()
    {
        __instance = *(global::Position.__Internal*)native;
    }

    public override int GetHashCode()
    {
        return __Instance.GetHashCode();
    }

    public double x
    {
        get
        {
            return __instance.x;
        }

        set
        {
            __instance.x = value;
        }
    }

    public double y
    {
        get
        {
            return __instance.y;
        }

        set
        {
            __instance.y = value;
        }
    }

    public double z
    {
        get
        {
            return __instance.z;
        }

        set
        {
            __instance.z = value;
        }
    }

    public double[] v
    {
        get
        {
            fixed (double* __arrPtr = __instance.v)
            {
                return CppSharp.Runtime.MarshalUtil.GetArray<double>(__arrPtr, 3);
            }
        }

        set
        {
            fixed (double* __arrPtr = __instance.v)
            {
                if (value != null)
                {
                    for (int i = 0; i < 3; i++)
                        __arrPtr[i] = value[i];
                }
            }
        }
    }
}

@JordanL8
Copy link
Author

Oh, I hadn't merged #1772 to our fork. I guess that will fix it, but I'm hitting: CS8170. I think I have a local fix but will test it out and close this issue if everything works.

@JordanL8
Copy link
Author

Yep, still looks to be an issue. This is specifically for a virtual method with an out param. So implemented in C# and called from C++ via the delegate, it seems a copy is still made in the create instance call in the delegate hook.

@JordanL8
Copy link
Author

JordanL8 commented Feb 20, 2024

For reference, to resolve Struct members cannot return 'this' or other instance members by reference, I changed the following in my fork:

if (@class.IsValueType)
{
    WriteLine($"private {@class.Name}.{Helpers.InternalStruct} {Helpers.InstanceField};");
    //WriteLine($"internal ref {@class.Name}.{Helpers.InternalStruct} {Helpers.InstanceIdentifier} => ref {Helpers.InstanceField};");
    
    WriteLine($"internal ref {@class.Name}.{Helpers.InternalStruct} {Helpers.InstanceIdentifier}");
    WriteOpenBraceAndIndent();
    WriteLine("get");
    WriteOpenBraceAndIndent();
    WriteLine($"fixed ({@class.Name}.{Helpers.InternalStruct}* ptr = &__instance)");
    WriteOpenBraceAndIndent();
    WriteLine("return ref *ptr;");
    UnindentAndWriteCloseBrace();
    UnindentAndWriteCloseBrace();
    UnindentAndWriteCloseBrace();
}

Which produces the following instead of the direct member ref return

internal ref <type>.__Internal __Instance
{
    get
    {
        fixed (<type>.__Internal* ptr = &__instance)
        {
            return ref *ptr;
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants