Ref/out value type method params not working correctly
JordanL8 opened this issue · 6 comments
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++.
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?
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.
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];
}
}
}
}
}
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.
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;
}
}
}