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

Cleanup VARIANT marshalling and convert to managed #102498

Open
wants to merge 46 commits into
base: main
Choose a base branch
from

Conversation

huoyaoyuan
Copy link
Member

Cleans up the managed Variant struct used as an intermediate value.

Basically, folds MarshalHelperConvertObjectToVariant with MarshalOleVariantForComVariant, MarshalHelperConvertVariantToObject and MarshalHelperCastVariant with MarshalComVariantForOleVariant.

Tests were first written and tested on main.

Array and record marshalling are still kept native. VT_ARRAY marshalling looks sharing many logic with non-variant marshalling.

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label May 21, 2024
Comment on lines 43 to 51
private static unsafe object? ConvertWrappedObject(object? wrapped) => wrapped switch
{
_objref = null;
_flags = CV_U4;
_data = val;
}

public Variant(long val)
null or Empty => null,
Enum => wrapped.GetType(),
IntPtr or UIntPtr => wrapped,
ValueType v when RuntimeHelpers.GetMethodTable(v)->IsPrimitive => null,
DateTime or TimeSpan or Currency => null,
_ => wrapped,
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explanation for this:
Previously, when UnknownWrapper or DispatchWrapper is passed in, the wrapped object goes through SetFieldObject, and the objRef field will be used as the real object passed to native.

Our managed test is exercising one case, but not exhaustive:

@huoyaoyuan
Copy link
Member Author

The failures looks unrelated now.

_flags = CV_I8;
_data = val;
}
// Cases handled at native side: string, bool, primitives including (U)IntPtr excluding U(Int)64, array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the case, I would handle them in the switch and throw or assert so we know what is going on.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is the caller from RECORD case of MarshalHelperCastVariant.

The pre-existing behavior is convert whatever received to VARIANT, and use VariantChangeType to attempt to convert it to VT_RECORD. However, VariantChangeType doesn't allow anything else to cast to VT_RECORD.

To determine whether an object will be casted to VT_RECORD, I'm making a call to MarshalHelperConvertObjectToVariant to execute all the rule in it. So these types are end up hittable and will be handled by the IConvertible case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also have a question about observable exception throwing behavior. Getting unmarshallable types (TimeSpan, SafeHandle etc) passed into CastVariant will throw corresponding exception. I'm currently only passing value types because they won't create things that need clean-up in the VARIANT.

// Helper code: on the back propagation path where a VT_BYREF VARIANT*
// is marshaled to a "ref Object", we use this helper to force the
// updated object back to the original type.
internal static void MarshalHelperCastVariant(object pValue, int vt, ref Variant v)
internal static void MarshalHelperCastVariant(object pValue, int vt, out ComVariant v)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we return here too?

Comment on lines 352 to 356
case VarEnum.VT_RECORD:
MarshalHelperConvertObjectToVariant(pValue, out v);
if (v.VarType != VarEnum.VT_RECORD)
throw new InvalidCastException(SR.InvalidCast_CannotCoerceByRefVariant);
break;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to restore the behavior here, and relying on the location to be cleaned up by RAII at native side.
Is there any better option for this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the previous behavior in this case? I'm not following the issue being hit here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any destructors in MarshalOleRefVariantForObject. Is that where you are talking about?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously in MarshalOleRefVariantForObject, if SafeVariantChangeType fails, would the data (potentially BSTR or something else) in vtmp be leaked? SafeVariantClear won't be called in throwing path.

I've concluded that SafeVariantChangeType is unnecessary and will always fail. The content of the VARIANT should be cleared at managed side in failing path.

@huoyaoyuan
Copy link
Member Author

@AaronRobinsonMSFT Can you answer remaining questions in this PR?

The concern is about cleanup in exceptional cases with partial result. When taking a ref at managed side, RAII at native side may take care of the cleanup.
I still want to keep the current implementation and shape as possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-Interop-coreclr community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants