From 778cc8436260736c1ca5393abb27414ea42ad8a7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 14 May 2024 11:49:12 +0800 Subject: [PATCH] Convert OAVariant interop to managed (#100176) * Convert BoxEnum to managed * SetFieldsObject to managed * Handle decimal and other CV_OBJECT * Managed ToOAVariant and FromOAVariant * Marshal for IUnknown and IDispatch * VariantChangeTypeEx interop * Use managed reflection for System.Drawing.Color conversion * Use MarshalNative for IDispatch/IUnknown marshalling * Move Color conversion to Variant * Respect VTToCV mapping * Improve test type coverage * Test for values in ReturnToManaged --------- Co-authored-by: Aaron Robinson --- .../src/Microsoft/Win32/OAVariantLib.cs | 188 +++++--- .../src/System/OleAutBinder.cs | 3 +- .../src/System/Variant.cs | 10 +- .../classlibnative/bcltype/CMakeLists.txt | 1 - .../classlibnative/bcltype/oavariant.cpp | 420 ------------------ .../classlibnative/bcltype/oavariant.h | 18 - .../classlibnative/bcltype/variant.cpp | 32 ++ src/coreclr/classlibnative/bcltype/variant.h | 3 + src/coreclr/vm/corelib.cpp | 1 - src/coreclr/vm/marshalnative.cpp | 27 ++ src/coreclr/vm/marshalnative.h | 5 + src/coreclr/vm/qcallentrypoints.cpp | 5 +- .../OleAut32/Interop.VariantChangeTypeEx.cs | 14 + src/libraries/Common/src/System/HResults.cs | 3 + .../System.Private.CoreLib.Shared.projitems | 3 + .../COM/NETClients/IDispatch/Program.cs | 63 ++- .../COM/NETServer/DispatchCoerceTesting.cs | 40 +- .../COM/NativeClients/Dispatch/Client.cpp | 147 +++--- .../COM/NativeServer/DispatchCoerceTesting.h | 52 ++- .../COM/ServerContracts/Server.Contracts.cs | 14 +- 20 files changed, 439 insertions(+), 610 deletions(-) delete mode 100644 src/coreclr/classlibnative/bcltype/oavariant.cpp delete mode 100644 src/coreclr/classlibnative/bcltype/oavariant.h create mode 100644 src/libraries/Common/src/Interop/Windows/OleAut32/Interop.VariantChangeTypeEx.cs diff --git a/src/coreclr/System.Private.CoreLib/src/Microsoft/Win32/OAVariantLib.cs b/src/coreclr/System.Private.CoreLib/src/Microsoft/Win32/OAVariantLib.cs index a5fbad6504f35..a51ec6f0132e4 100644 --- a/src/coreclr/System.Private.CoreLib/src/Microsoft/Win32/OAVariantLib.cs +++ b/src/coreclr/System.Private.CoreLib/src/Microsoft/Win32/OAVariantLib.cs @@ -13,11 +13,13 @@ ===========================================================*/ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; namespace Microsoft.Win32 { @@ -26,48 +28,32 @@ internal static unsafe partial class OAVariantLib #region Constants // Constants for VariantChangeType from OleAuto.h - public const int NoValueProp = 0x01; - public const int AlphaBool = 0x02; - public const int NoUserOverride = 0x04; - public const int CalendarHijri = 0x08; public const int LocalBool = 0x10; - internal static readonly Type?[] ClassTypes = { - typeof(Empty), - typeof(void), - typeof(bool), - typeof(char), - typeof(sbyte), - typeof(byte), - typeof(short), - typeof(ushort), - typeof(int), - typeof(uint), - typeof(long), - typeof(ulong), - typeof(float), - typeof(double), - typeof(string), - typeof(void), - typeof(DateTime), - typeof(TimeSpan), - typeof(object), - typeof(decimal), - null, // Enums - what do we do here? - typeof(Missing), - typeof(DBNull), + private static readonly Dictionary ClassTypes = new Dictionary + { + { typeof(bool), VarEnum.VT_BOOL }, + { typeof(char), VarEnum.VT_I2 }, + { typeof(sbyte), VarEnum.VT_I1 }, + { typeof(byte), VarEnum.VT_UI1 }, + { typeof(short), VarEnum.VT_I2 }, + { typeof(ushort), VarEnum.VT_UI2 }, + { typeof(int), VarEnum.VT_I4 }, + { typeof(uint), VarEnum.VT_UI4 }, + { typeof(long), VarEnum.VT_I8 }, + { typeof(ulong), VarEnum.VT_UI8 }, + { typeof(float), VarEnum.VT_R4 }, + { typeof(double), VarEnum.VT_R8 }, + { typeof(string), VarEnum.VT_BSTR }, + { typeof(DateTime), VarEnum.VT_DATE }, + { typeof(decimal), VarEnum.VT_DECIMAL }, }; - // Keep these numbers in sync w/ the above array. - private const int CV_OBJECT = 0x12; - #endregion #region Internal Methods -#pragma warning disable CS8500 - /** * Changes a Variant from one type to another, calling the OLE * Automation VariantChangeTypeEx routine. Note the legal types here are @@ -75,53 +61,129 @@ internal static unsafe partial class OAVariantLib * Variant and the types that CLR supports explicitly in the * CLR Variant class. */ - internal static Variant ChangeType(Variant source, Type targetClass, short options, CultureInfo culture) + internal static object? ChangeType(object source, Type targetClass, short options, CultureInfo culture) { ArgumentNullException.ThrowIfNull(targetClass); ArgumentNullException.ThrowIfNull(culture); - Variant result = default; - ChangeType( - &result, - &source, - culture.LCID, - targetClass.TypeHandle.Value, - GetCVTypeFromClass(targetClass), - options); - return result; - } + object? result = null; - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "OAVariant_ChangeType")] - private static partial void ChangeType(Variant* result, Variant* source, int lcid, IntPtr typeHandle, int cvType, short flags); + if (Variant.IsSystemDrawingColor(targetClass)) + { + if (source is int || source is uint) + { + uint sourceData = source is int ? (uint)(int)source : (uint)source; + // Int32/UInt32 can be converted to System.Drawing.Color + Variant.ConvertOleColorToSystemColor(ObjectHandleOnStack.Create(ref result), sourceData, targetClass.TypeHandle.Value); + Debug.Assert(result != null); + return result; + } + } -#pragma warning restore CS8500 + if (!ClassTypes.TryGetValue(targetClass, out VarEnum vt)) + { + throw new NotSupportedException(SR.NotSupported_ChangeType); + } - #endregion + ComVariant vOp = ToOAVariant(source); + ComVariant ret = default; + int hr = Interop.OleAut32.VariantChangeTypeEx(&ret, &vOp, culture.LCID, options, (ushort)vt); - #region Private Helpers + using (vOp) + using (ret) + { + if (hr < 0) + { + OAFailed(hr); + } - private static int GetCVTypeFromClass(Type ctype) - { - Debug.Assert(ctype != null); - Debug.Assert(ClassTypes[CV_OBJECT] == typeof(object), "OAVariantLib::ClassTypes[CV_OBJECT] == Object.class"); + result = FromOAVariant(ret); + if (targetClass == typeof(char)) + { + result = (char)(uint)result!; + } + } - // OleAut Binder works better if unrecognized - // types were changed to Object. - int cvtype = CV_OBJECT; + return result; + } - for (int i = 0; i < ClassTypes.Length; i++) + private static void OAFailed(int hr) + { + switch (hr) { - if (ctype.Equals(ClassTypes[i])) - { - cvtype = i; - break; - } + case HResults.COR_E_OUTOFMEMORY: + throw new OutOfMemoryException(); + case HResults.DISP_E_BADVARTYPE: + throw new NotSupportedException(SR.NotSupported_OleAutBadVarType); + case HResults.DISP_E_DIVBYZERO: + throw new DivideByZeroException(); + case HResults.DISP_E_OVERFLOW: + throw new OverflowException(); + case HResults.DISP_E_TYPEMISMATCH: + throw new InvalidCastException(SR.InvalidCast_OATypeMismatch); + case HResults.E_INVALIDARG: + throw new ArgumentException(); + default: + Debug.Fail("Unrecognized HResult - OAVariantLib routine failed in an unexpected way!"); + throw Marshal.GetExceptionForHR(hr); } + } - return cvtype; + private static ComVariant ToOAVariant(object input) + { + return input switch + { + string str => ComVariant.Create(str), + DateTime dateTime => ComVariant.Create(dateTime), + bool b => ComVariant.Create(b), + decimal d => ComVariant.Create(d), + sbyte i1 => ComVariant.Create(i1), + byte u1 => ComVariant.Create(u1), + short i2 => ComVariant.Create(i2), + ushort u2 => ComVariant.Create(u2), + int i4 => ComVariant.Create(i4), + uint u4 => ComVariant.Create(u4), + long i8 => ComVariant.Create(i8), + ulong u8 => ComVariant.Create(u8), + float r4 => ComVariant.Create(r4), + double r8 => ComVariant.Create(r8), + null => default, + Missing => throw new NotSupportedException(SR.NotSupported_ChangeType), + DBNull => ComVariant.Null, + _ => GetComIPFromObjectRef(input) // Convert the object to an IDispatch/IUnknown pointer. + }; + } + + private static ComVariant GetComIPFromObjectRef(object? obj) + { + IntPtr pUnk = GetIUnknownOrIDispatchForObject(ObjectHandleOnStack.Create(ref obj), out bool isIDispatch); + return ComVariant.CreateRaw(isIDispatch ? VarEnum.VT_DISPATCH : VarEnum.VT_UNKNOWN, pUnk); } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MarshalNative_GetIUnknownOrIDispatchForObject")] + private static partial IntPtr GetIUnknownOrIDispatchForObject(ObjectHandleOnStack o, [MarshalAs(UnmanagedType.Bool)] out bool isIDispatch); + + private static object? FromOAVariant(ComVariant input) => + input.VarType switch + { + VarEnum.VT_BSTR => input.As(), + VarEnum.VT_DATE => input.As(), + VarEnum.VT_BOOL => input.As(), + VarEnum.VT_DECIMAL => input.As(), + VarEnum.VT_I1 => input.As(), + VarEnum.VT_UI1 => input.As(), + VarEnum.VT_I2 => input.As(), + VarEnum.VT_UI2 => input.As(), + VarEnum.VT_I4 or VarEnum.VT_INT => input.As(), + VarEnum.VT_UI4 or VarEnum.VT_UINT => input.As(), + VarEnum.VT_I8 => input.As(), + VarEnum.VT_UI8 => input.As(), + VarEnum.VT_R4 => input.As(), + VarEnum.VT_R8 => input.As(), + _ => throw new NotSupportedException(SR.NotSupported_ChangeType), + }; + #endregion } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/OleAutBinder.cs b/src/coreclr/System.Private.CoreLib/src/System/OleAutBinder.cs index d1072ff86ec8a..32a69bb155f14 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/OleAutBinder.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/OleAutBinder.cs @@ -18,7 +18,6 @@ internal sealed class OleAutBinder : DefaultBinder // This binder uses OLEAUT to change the type of the variant. public override object ChangeType(object value, Type type, CultureInfo? cultureInfo) { - Variant myValue = new Variant(value); cultureInfo ??= CultureInfo.CurrentCulture; #if DISPLAY_DEBUG_INFO @@ -62,7 +61,7 @@ public override object ChangeType(object value, Type type, CultureInfo? cultureI #endif // Specify the LocalBool flag to have BOOL values converted to local language rather // than 0 or -1. - object RetObj = OAVariantLib.ChangeType(myValue, type, OAVariantLib.LocalBool, cultureInfo).ToObject()!; + object RetObj = OAVariantLib.ChangeType(value, type, OAVariantLib.LocalBool, cultureInfo)!; #if DISPLAY_DEBUG_INFO Console.WriteLine("Object returned from ChangeType is of type: " + RetObj.GetType().Name); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Variant.cs b/src/coreclr/System.Private.CoreLib/src/System/Variant.cs index b945b28e08a75..f32f2b99e788a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Variant.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Variant.cs @@ -17,7 +17,7 @@ namespace System { - internal struct Variant + internal partial struct Variant { // Do Not change the order of these fields. // They are mapped to the native VariantData * data structure. @@ -70,6 +70,14 @@ internal struct Variant internal static Variant Missing => new Variant(CV_MISSING, Type.Missing, 0); internal static Variant DBNull => new Variant(CV_NULL, System.DBNull.Value, 0); + internal static bool IsSystemDrawingColor(Type type) => type.FullName == "System.Drawing.Color"; // Matches the behavior of IsTypeRefOrDef + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Variant_ConvertSystemColorToOleColor")] + internal static partial uint ConvertSystemColorToOleColor(ObjectHandleOnStack obj); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Variant_ConvertOleColorToSystemColor")] + internal static partial void ConvertOleColorToSystemColor(ObjectHandleOnStack objret, uint value, IntPtr pMT); + // // Native Methods // diff --git a/src/coreclr/classlibnative/bcltype/CMakeLists.txt b/src/coreclr/classlibnative/bcltype/CMakeLists.txt index 8e4baf95751e3..e8cce56d17355 100644 --- a/src/coreclr/classlibnative/bcltype/CMakeLists.txt +++ b/src/coreclr/classlibnative/bcltype/CMakeLists.txt @@ -2,7 +2,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(BCLTYPE_SOURCES arraynative.cpp - oavariant.cpp objectnative.cpp system.cpp varargsnative.cpp diff --git a/src/coreclr/classlibnative/bcltype/oavariant.cpp b/src/coreclr/classlibnative/bcltype/oavariant.cpp deleted file mode 100644 index ac8c55f359d80..0000000000000 --- a/src/coreclr/classlibnative/bcltype/oavariant.cpp +++ /dev/null @@ -1,420 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// -// File: OAVariant.cpp -// - -#include - -#ifdef FEATURE_COMINTEROP - -#include -#include "excep.h" -#include "oavariant.h" -#include "comdatetime.h" // DateTime <-> OleAut date conversions -#include "interoputil.h" -#include "interopconverter.h" -#include "excep.h" -#include "string.h" -#include "comutilnative.h" // for COMDate - -#define INVALID_MAPPING (BYTE)(-1) - -static const BYTE CVtoVTTable [] = -{ - VT_EMPTY, // CV_EMPTY - VT_VOID, // CV_VOID - VT_BOOL, // CV_BOOLEAN - VT_UI2, // CV_CHAR - VT_I1, // CV_I1 - VT_UI1, // CV_U1 - VT_I2, // CV_I2 - VT_UI2, // CV_U2 - VT_I4, // CV_I4 - VT_UI4, // CV_U4 - VT_I8, // CV_I8 - VT_UI8, // CV_U8 - VT_R4, // CV_R4 - VT_R8, // CV_R8 - VT_BSTR, // CV_STRING - INVALID_MAPPING, // CV_PTR - VT_DATE, // CV_DATETIME - INVALID_MAPPING, // CV_TIMESPAN - VT_UNKNOWN, // CV_OBJECT - VT_DECIMAL, // CV_DECIMAL - VT_CY, // CV_CURRENCY - INVALID_MAPPING, // CV_ENUM - INVALID_MAPPING, // CV_MISSING - VT_NULL, // CV_NULL - INVALID_MAPPING // CV_LAST -}; - -static const BYTE VTtoCVTable[] = -{ - CV_EMPTY, // VT_EMPTY - CV_NULL, // VT_NULL - CV_I2, // VT_I2 - CV_I4, // VT_I4 - CV_R4, // VT_R4 - CV_R8, // VT_R8 - CV_CURRENCY,// VT_CY - CV_DATETIME,// VT_DATE - CV_STRING, // VT_BSTR - INVALID_MAPPING, // VT_DISPATCH - INVALID_MAPPING, // VT_ERROR - CV_BOOLEAN, // VT_BOOL - CV_OBJECT, // VT_VARIANT - CV_OBJECT, // VT_UNKNOWN - CV_DECIMAL, // VT_DECIMAL - INVALID_MAPPING, // An unused enum table entry - CV_I1, // VT_I1 - CV_U1, // VT_UI1 - CV_U2, // VT_UI2 - CV_U4, // VT_UI4 - CV_I8, // VT_I8 - CV_U8, // VT_UI8 - CV_I4, // VT_INT - CV_U4, // VT_UINT - CV_VOID // VT_VOID -}; - -// Need translations from CVType to VARENUM and vice versa. CVTypes -// is defined in olevariant.h. VARENUM is defined in OleAut's variant.h -// Assumption here is we will only deal with VARIANTs and not other OLE -// constructs such as property sets or safe arrays. -static VARENUM CVtoVT(const CVTypes cv) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(cv >= 0 && cv < CV_LAST); - } - CONTRACTL_END; - - if (CVtoVTTable[cv] == INVALID_MAPPING) - COMPlusThrow(kNotSupportedException, W("NotSupported_ChangeType")); - - return (VARENUM) CVtoVTTable[cv]; -} - -// Need translations from CVType to VARENUM and vice versa. CVTypes -// is defined in olevariant.h. VARENUM is defined in OleAut's variant.h -static CVTypes VTtoCV(const VARENUM vt) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(vt < VT_VOID); - } - CONTRACTL_END; - - if (vt <0 || vt > VT_VOID || VTtoCVTable[vt]==INVALID_MAPPING) - COMPlusThrow(kNotSupportedException, W("NotSupported_ChangeType")); - - return (CVTypes) VTtoCVTable[vt]; -} - - -// Converts a COM+ Variant to an OleAut Variant. Returns true if -// there was a native object allocated by this method that must be freed, -// else false. -static bool ToOAVariant(VariantData const* src, VARIANT* oa) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - INJECT_FAULT(COMPlusThrowOM()); - PRECONDITION(CheckPointer(src)); - PRECONDITION(CheckPointer(oa)); - } - CONTRACTL_END; - - SafeVariantInit(oa); - UINT64 * dest = (UINT64*) &V_UI1(oa); - *dest = 0; - - WCHAR * chars; - int strLen; - - // Set the data field of the OA Variant to be either the object reference - // or the data (ie int) that it needs. - - switch (src->GetType()) - { - case CV_STRING: - if (src->GetObjRef() == NULL) - { - V_BSTR(oa) = NULL; - V_VT(oa) = static_cast(CVtoVT(src->GetType())); - - // OA perf feature: VarClear calls SysFreeString(null), which access violates. - return false; - } - - ((STRINGREF) (src->GetObjRef()))->RefInterpretGetStringValuesDangerousForGC(&chars, &strLen); - V_BSTR(oa) = SysAllocStringLen(chars, strLen); - if (V_BSTR(oa) == NULL) - COMPlusThrowOM(); - - V_VT(oa) = static_cast(CVtoVT(src->GetType())); - - return true; - - case CV_CHAR: - chars = (WCHAR*)src->GetData(); - V_BSTR(oa) = SysAllocStringLen(chars, 1); - if (V_BSTR(oa) == NULL) - COMPlusThrowOM(); - - // We should override the VTtoVT default of VT_UI2 for this case. - V_VT(oa) = VT_BSTR; - - return true; - - case CV_DATETIME: - V_DATE(oa) = COMDateTime::TicksToDoubleDate(src->GetDataAsInt64()); - V_VT(oa) = static_cast(CVtoVT(src->GetType())); - return false; - - case CV_BOOLEAN: - V_BOOL(oa) = (src->GetDataAsInt64()==0 ? VARIANT_FALSE : VARIANT_TRUE); - V_VT(oa) = static_cast(CVtoVT(src->GetType())); - return false; - - case CV_DECIMAL: - { - OBJECTREF obj = src->GetObjRef(); - DECIMAL * d = (DECIMAL*) obj->GetData(); - // DECIMALs and Variants are the same size. Variants are a union between - // all the normal Variant fields (vt, bval, etc) and a Decimal. Decimals - // also have the first 2 bytes reserved, for a VT field. - - V_DECIMAL(oa) = *d; - V_VT(oa) = VT_DECIMAL; - return false; - } - - case CV_OBJECT: - { - OBJECTREF obj = src->GetObjRef(); - GCPROTECT_BEGIN(obj) - { - IUnknown *pUnk = NULL; - - // Convert the object to an IDispatch/IUnknown pointer. - ComIpType FetchedIpType = ComIpType_None; - pUnk = GetComIPFromObjectRef(&obj, ComIpType_Both, &FetchedIpType); - V_UNKNOWN(oa) = pUnk; - V_VT(oa) = static_cast(FetchedIpType == ComIpType_Dispatch ? VT_DISPATCH : VT_UNKNOWN); - } - GCPROTECT_END(); - return true; - } - - default: - *dest = src->GetDataAsInt64(); - V_VT(oa) = static_cast(CVtoVT(src->GetType())); - return false; - } -} - -// Converts an OleAut Variant into a COM+ Variant. -// Note that we pass the VariantData Byref so that if GC happens, 'var' gets updated -static void FromOAVariant(VARIANT const* src, VariantData*& var) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - INJECT_FAULT(COMPlusThrowOM()); - PRECONDITION(CheckPointer(src)); - } - CONTRACTL_END; - - // Clear the return variant value. It's allocated on - // the stack and we only want valid state data in there. - memset(var, 0, sizeof(VariantData)); - - CVTypes type = VTtoCV((VARENUM) V_VT(src)); - var->SetType(type); - - switch (type) - { - case CV_STRING: - { - // BSTRs have an int with the string buffer length (not the string length) - // followed by the data. The pointer to the BSTR points to the start of the - // characters, NOT the start of the BSTR. - WCHAR* chars = V_BSTR(src); - int strLen = SysStringLen(V_BSTR(src)); - STRINGREF str = StringObject::NewString(chars, strLen); - var->SetObjRef((OBJECTREF)str); - break; - } - case CV_DATETIME: - var->SetDataAsInt64(COMDateTime::DoubleDateToTicks(V_DATE(src))); - break; - - case CV_BOOLEAN: - var->SetDataAsInt64(V_BOOL(src) == VARIANT_FALSE ? FALSE : TRUE); - break; - - case CV_DECIMAL: - { - MethodTable * pDecimalMT = GetTypeHandleForCVType(CV_DECIMAL).GetMethodTable(); - _ASSERTE(pDecimalMT); - OBJECTREF pDecimalRef = AllocateObject(pDecimalMT); - - *(DECIMAL *) pDecimalRef->GetData() = V_DECIMAL(src); - var->SetObjRef(pDecimalRef); - break; - } - - // All types less than 4 bytes need an explicit cast from their original - // type to be sign extended to 8 bytes. This makes Variant's ToInt32 - // function simpler for these types. - case CV_I1: - var->SetDataAsInt64(V_I1(src)); - break; - - case CV_U1: - var->SetDataAsInt64(V_UI1(src)); - break; - - case CV_I2: - var->SetDataAsInt64(V_I2(src)); - break; - - case CV_U2: - var->SetDataAsInt64(V_UI2(src)); - break; - - case CV_EMPTY: - case CV_NULL: - // Must set up the Variant's m_or to the appropriate classes. - // Note that OleAut doesn't have any VT_MISSING. - VariantData::NewVariant(var, type, NULL DEBUG_ARG(TRUE)); - break; - - case CV_OBJECT: - { - // Convert the IUnknown pointer to an OBJECTREF. - OBJECTREF oref = NULL; - GCPROTECT_BEGIN(oref); - GetObjectRefFromComIP(&oref, V_UNKNOWN(src)); - var->SetObjRef(oref); - GCPROTECT_END(); - break; - } - default: - // Copy all the bits there, and make sure we don't do any float to int conversions. - void* data = (void*)&(V_UI1(src)); - var->SetData(data); - break; - } -} - -static void OAFailed(HRESULT hr) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(FAILED(hr)); - } - CONTRACTL_END; - - switch (hr) - { - case E_OUTOFMEMORY: - COMPlusThrowOM(); - - case DISP_E_BADVARTYPE: - COMPlusThrow(kNotSupportedException, W("NotSupported_OleAutBadVarType")); - - case DISP_E_DIVBYZERO: - COMPlusThrow(kDivideByZeroException); - - case DISP_E_OVERFLOW: - COMPlusThrow(kOverflowException); - - case DISP_E_TYPEMISMATCH: - COMPlusThrow(kInvalidCastException, W("InvalidCast_OATypeMismatch")); - - case E_INVALIDARG: - COMPlusThrow(kArgumentException); - - default: - _ASSERTE(!"Unrecognized HResult - OAVariantLib routine failed in an unexpected way!"); - COMPlusThrowHR(hr); - } -} - -extern "C" void QCALLTYPE OAVariant_ChangeType(VariantData* result, VariantData* source, LCID lcid, void* targetType, int cvType, INT16 flags) -{ - CONTRACTL - { - QCALL_CHECK; - PRECONDITION(CheckPointer(result)); - } - CONTRACTL_END; - - BEGIN_QCALL; - - GCX_COOP(); - - bool converted = false; - - TypeHandle thTarget = TypeHandle::FromPtr(targetType); - if (cvType == CV_OBJECT && IsTypeRefOrDef(g_ColorClassName, thTarget.GetModule(), thTarget.GetCl())) - { - CVTypes sourceType = source->GetType(); - if (sourceType == CV_I4 || sourceType == CV_U4) - { - // Int32/UInt32 can be converted to System.Drawing.Color - SYSTEMCOLOR SystemColor; - ConvertOleColorToSystemColor(source->GetDataAsUInt32(), &SystemColor); - - result->SetObjRef(thTarget.AsMethodTable()->Box(&SystemColor)); - result->SetType(CV_OBJECT); - - converted = true; - } - } - - if (!converted) - { - VariantHolder ret; - VariantHolder vOp; - - VARENUM vt = CVtoVT((CVTypes) cvType); - ToOAVariant(source, &vOp); - - HRESULT hr = SafeVariantChangeTypeEx(&ret, &vOp, lcid, flags, static_cast(vt)); - - if (FAILED(hr)) - OAFailed(hr); - - if ((CVTypes) cvType == CV_CHAR) - { - result->SetType(CV_CHAR); - result->SetDataAsUInt16(V_UI2(&ret)); - } - else - { - FromOAVariant(&ret, result); - } - } - - END_QCALL; -} - -#endif // FEATURE_COMINTEROP diff --git a/src/coreclr/classlibnative/bcltype/oavariant.h b/src/coreclr/classlibnative/bcltype/oavariant.h deleted file mode 100644 index 2ee4de551b693..0000000000000 --- a/src/coreclr/classlibnative/bcltype/oavariant.h +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// -// File: OAVariant.h -// - -#ifndef _OAVARIANT_H_ -#define _OAVARIANT_H_ - -#ifndef FEATURE_COMINTEROP -#error FEATURE_COMINTEROP is required for this file -#endif // FEATURE_COMINTEROP - -#include "olevariant.h" - -extern "C" void QCALLTYPE OAVariant_ChangeType(VariantData* result, VariantData* source, LCID lcid, void* targetType, int cvType, INT16 flags); - -#endif // _OAVARIANT_H_ diff --git a/src/coreclr/classlibnative/bcltype/variant.cpp b/src/coreclr/classlibnative/bcltype/variant.cpp index a269644d79700..1a3cf7e75bb44 100644 --- a/src/coreclr/classlibnative/bcltype/variant.cpp +++ b/src/coreclr/classlibnative/bcltype/variant.cpp @@ -20,6 +20,7 @@ #include "vars.hpp" #include "variant.h" #include "string.h" + #include "field.h" // The following values are used to represent underlying @@ -286,4 +287,35 @@ int COMVariant::GetEnumFlags(TypeHandle th) } } +extern "C" uint32_t QCALLTYPE Variant_ConvertSystemColorToOleColor(QCall::ObjectHandleOnStack obj) +{ + QCALL_CONTRACT; + + uint32_t ret = 0; + + BEGIN_QCALL; + + GCX_COOP(); + OBJECTREF srcObj = obj.Get(); + ret = ConvertSystemColorToOleColor(&srcObj); + + END_QCALL; + + return ret; +} + +extern "C" void QCALLTYPE Variant_ConvertOleColorToSystemColor(QCall::ObjectHandleOnStack objRet, uint32_t oleColor, MethodTable* pMT) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + GCX_COOP(); + SYSTEMCOLOR systemColor{}; + ConvertOleColorToSystemColor(oleColor, &systemColor); + objRet.Set(pMT->Box(&systemColor)); + + END_QCALL; +} + #endif // FEATURE_COMINTEROP diff --git a/src/coreclr/classlibnative/bcltype/variant.h b/src/coreclr/classlibnative/bcltype/variant.h index aae0a5f9741ac..12c5a3d36d53d 100644 --- a/src/coreclr/classlibnative/bcltype/variant.h +++ b/src/coreclr/classlibnative/bcltype/variant.h @@ -40,5 +40,8 @@ class COMVariant static int GetEnumFlags(TypeHandle th); }; +extern "C" uint32_t QCALLTYPE Variant_ConvertSystemColorToOleColor(QCall::ObjectHandleOnStack obj); +extern "C" void QCALLTYPE Variant_ConvertOleColorToSystemColor(QCall::ObjectHandleOnStack objRet, uint32_t oleColor, MethodTable* pMT); + #endif // _VARIANT_H_ diff --git a/src/coreclr/vm/corelib.cpp b/src/coreclr/vm/corelib.cpp index c0a217593adc5..e172f6a5423e4 100644 --- a/src/coreclr/vm/corelib.cpp +++ b/src/coreclr/vm/corelib.cpp @@ -47,7 +47,6 @@ #ifdef FEATURE_COMINTEROP #include "variant.h" -#include "oavariant.h" #include "mngstdinterfaces.h" #endif // FEATURE_COMINTEROP diff --git a/src/coreclr/vm/marshalnative.cpp b/src/coreclr/vm/marshalnative.cpp index 1406458b11d36..a71ec7d00c49b 100644 --- a/src/coreclr/vm/marshalnative.cpp +++ b/src/coreclr/vm/marshalnative.cpp @@ -536,6 +536,33 @@ extern "C" IDispatch* QCALLTYPE MarshalNative_GetIDispatchForObject(QCall::Objec return retVal; } +//==================================================================== +// return the IUnknown* or IDispatch* for an Object. +//==================================================================== +extern "C" void* QCALLTYPE MarshalNative_GetIUnknownOrIDispatchForObject(QCall::ObjectHandleOnStack o, BOOL* isIDispatch) +{ + QCALL_CONTRACT; + + void* retVal = NULL; + + BEGIN_QCALL; + + // Ensure COM is started up. + EnsureComStarted(); + + GCX_COOP(); + + OBJECTREF oref = o.Get(); + GCPROTECT_BEGIN(oref); + ComIpType fetchedIpType = ComIpType_None; + retVal = GetComIPFromObjectRef(&oref, ComIpType_Both, &fetchedIpType); + *isIDispatch = fetchedIpType == ComIpType_Dispatch; + GCPROTECT_END(); + + END_QCALL; + return retVal; +} + //==================================================================== // return the IUnknown* representing the interface for the Object // Object o should support Type T diff --git a/src/coreclr/vm/marshalnative.h b/src/coreclr/vm/marshalnative.h index f88b689baa5ae..ed9f67c0dc507 100644 --- a/src/coreclr/vm/marshalnative.h +++ b/src/coreclr/vm/marshalnative.h @@ -77,6 +77,11 @@ extern "C" IUnknown* QCALLTYPE MarshalNative_GetIUnknownForObject(QCall::ObjectH //==================================================================== extern "C" IDispatch* QCALLTYPE MarshalNative_GetIDispatchForObject(QCall::ObjectHandleOnStack o); +//==================================================================== +// return the IUnknown* or IDispatch* for an Object. +//==================================================================== +extern "C" void* QCALLTYPE MarshalNative_GetIUnknownOrIDispatchForObject(QCall::ObjectHandleOnStack o, BOOL* isIDispatch); + //==================================================================== // return the IUnknown* representing the interface for the Object // Object o should support Type T diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 99e76824a5df9..e038db62831d9 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -43,7 +43,6 @@ #ifdef FEATURE_COMINTEROP #include "variant.h" -#include "oavariant.h" #include "mngstdinterfaces.h" #endif // FEATURE_COMINTEROP @@ -285,6 +284,7 @@ static const Entry s_QCall[] = DllImportEntry(MarshalNative_GetTypeFromCLSID) DllImportEntry(MarshalNative_GetIUnknownForObject) DllImportEntry(MarshalNative_GetIDispatchForObject) + DllImportEntry(MarshalNative_GetIUnknownOrIDispatchForObject) DllImportEntry(MarshalNative_GetComInterfaceForObject) DllImportEntry(MarshalNative_GetObjectForIUnknown) DllImportEntry(MarshalNative_GetUniqueObjectForIUnknown) @@ -318,7 +318,8 @@ static const Entry s_QCall[] = DllImportEntry(MngdSafeArrayMarshaler_ConvertSpaceToManaged) DllImportEntry(MngdSafeArrayMarshaler_ConvertContentsToManaged) DllImportEntry(MngdSafeArrayMarshaler_ClearNative) - DllImportEntry(OAVariant_ChangeType) + DllImportEntry(Variant_ConvertSystemColorToOleColor) + DllImportEntry(Variant_ConvertOleColorToSystemColor) #endif // FEATURE_COMINTEROP DllImportEntry(NativeLibrary_LoadFromPath) DllImportEntry(NativeLibrary_LoadByName) diff --git a/src/libraries/Common/src/Interop/Windows/OleAut32/Interop.VariantChangeTypeEx.cs b/src/libraries/Common/src/Interop/Windows/OleAut32/Interop.VariantChangeTypeEx.cs new file mode 100644 index 0000000000000..edb77c858b381 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/OleAut32/Interop.VariantChangeTypeEx.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class OleAut32 + { + [LibraryImport(Libraries.OleAut32)] + internal static unsafe partial int VariantChangeTypeEx(void* pVarRes, void* pVarSrc, int lcid, short wFlags, ushort vt); + } +} diff --git a/src/libraries/Common/src/System/HResults.cs b/src/libraries/Common/src/System/HResults.cs index bbc434b87289c..b0de5f2c5fe35 100644 --- a/src/libraries/Common/src/System/HResults.cs +++ b/src/libraries/Common/src/System/HResults.cs @@ -109,7 +109,10 @@ internal static partial class HResults internal const int COR_E_VERIFICATION = unchecked((int)0x8013150D); internal const int COR_E_WAITHANDLECANNOTBEOPENED = unchecked((int)0x8013152C); internal const int CO_E_NOTINITIALIZED = unchecked((int)0x800401F0); + internal const int DISP_E_TYPEMISMATCH = unchecked((int)0x80020005); + internal const int DISP_E_BADVARTYPE = unchecked((int)0x80020008); internal const int DISP_E_OVERFLOW = unchecked((int)0x8002000A); + internal const int DISP_E_DIVBYZERO = unchecked((int)0x80020012); internal const int E_BOUNDS = unchecked((int)0x8000000B); internal const int E_CHANGED_STATE = unchecked((int)0x8000000C); internal const int E_FILENOTFOUND = unchecked((int)0x80070002); diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index ce22178ca8a0f..d2a72950212df 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2087,6 +2087,9 @@ Common\Interop\Windows\OleAut32\Interop.SysFreeString.cs + + Common\Interop\Windows\OleAut32\Interop.VariantChangeTypeEx.cs + Common\Interop\Windows\Ole32\Interop.CLSIDFromProgID.cs diff --git a/src/tests/Interop/COM/NETClients/IDispatch/Program.cs b/src/tests/Interop/COM/NETClients/IDispatch/Program.cs index f62616dca3622..69865c8f97b82 100644 --- a/src/tests/Interop/COM/NETClients/IDispatch/Program.cs +++ b/src/tests/Interop/COM/NETClients/IDispatch/Program.cs @@ -5,6 +5,7 @@ namespace NetClient { using System; + using System.Drawing; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; @@ -245,11 +246,6 @@ static void Validate_ValueCoerce_ReturnToManaged() Assert.Equal(expected, result); } - // Invalid: Rejected before reaching coerce - Console.WriteLine("Invalid variant type should throw InvalidOleVariantTypeException."); - var variantException = Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged(0x7FFF)); - Assert.Equal(unchecked((int)0x80131531), variantException.HResult); - // Not supported source or destination type: COMException { HResult: 0x80020005 } // DISP_E_PARAMNOTFOUND: Converts to Missing @@ -257,20 +253,61 @@ static void Validate_ValueCoerce_ReturnToManaged() var comException = Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged(unchecked((short)((short)VarEnum.VT_ERROR | 0x8000)))); Assert.Equal(unchecked((int)0x80020005), comException.HResult); + // Types rejected by OAVariantLib + VarEnum[] unsupportedTypes = + { + VarEnum.VT_ERROR | (VarEnum)0x8000, + }; + + // Types rejected by VariantChangeTypeEx + VarEnum[] invalidCastTypes = + { + VarEnum.VT_UNKNOWN, + VarEnum.VT_NULL, + }; + + foreach (var vt in invalidCastTypes) + { + Console.WriteLine($"Converting {vt} to int should fail from VariantChangeTypeEx."); + Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged((short)vt)); + } + + // Invalid: Rejected before reaching coerce + Console.WriteLine("Invalid variant type should throw InvalidOleVariantTypeException."); + var variantException = Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged(0x7FFF)); + Assert.Equal(unchecked((int)0x80131531), variantException.HResult); + + Console.WriteLine("Invoking void-returning method should not allocate return buffer."); + // E_POINTER translates to NullReferenceException + Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged_Void(0)); + + Console.WriteLine("Converting int to double should be supported."); + Assert.Equal(1234d, dispatchCoerceTesting.ReturnToManaged_Double(1234)); + + Console.WriteLine("Converting int to string should be supported."); + Assert.Equal("1234", dispatchCoerceTesting.ReturnToManaged_String(1234)); + + Console.WriteLine("Converting int to decimal should be supported."); + Assert.Equal(1234m, dispatchCoerceTesting.ReturnToManaged_Decimal(1234)); + + Console.WriteLine("Converting int to DateTime should be supported."); + Assert.Equal(new DateTime(100, 1, 1), dispatchCoerceTesting.ReturnToManaged_DateTime(-657434)); + Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged_DateTime(-657435)); + Assert.Equal(new DateTime(9999, 12, 31), dispatchCoerceTesting.ReturnToManaged_DateTime(2958465)); + Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged_DateTime(2958466)); + + Console.WriteLine("Converting int to System.Drawing.Color should be supported."); + Assert.Equal(Color.FromKnownColor(KnownColor.ActiveBorder), dispatchCoerceTesting.ReturnToManaged_Color(unchecked((int)0x8000000A))); + Assert.Equal(ColorTranslator.FromOle(1234), dispatchCoerceTesting.ReturnToManaged_Color(1234)); + Console.WriteLine("Converting int to VT_MISSING should be rejected."); - comException = Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged_Missing()); + comException = Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged_Missing(0)); Assert.Equal(unchecked((int)0x80020005), comException.HResult); Console.WriteLine("Converting int to VT_NULL should be rejected."); - comException = Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged_DBNull()); + comException = Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged_DBNull(0)); Assert.Equal(unchecked((int)0x80020005), comException.HResult); - // Rejected by VariantChangeTypeEx - Console.WriteLine("Converting VT_UNKNOWN to int should fail from VariantChangeTypeEx."); - Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged((short)VarEnum.VT_UNKNOWN)); - Console.WriteLine("Converting VT_NULL to int should fail from VariantChangeTypeEx."); - Assert.Throws(() => dispatchCoerceTesting.ReturnToManaged((short)VarEnum.VT_NULL)); - // LOCAL_BOOL Console.WriteLine("VARIANT_BOOL should convert to non-numeric string."); Assert.Equal("True", dispatchCoerceTesting.BoolToString()); diff --git a/src/tests/Interop/COM/NETServer/DispatchCoerceTesting.cs b/src/tests/Interop/COM/NETServer/DispatchCoerceTesting.cs index 0367c605df483..37330058801eb 100644 --- a/src/tests/Interop/COM/NETServer/DispatchCoerceTesting.cs +++ b/src/tests/Interop/COM/NETServer/DispatchCoerceTesting.cs @@ -22,18 +22,48 @@ public int ManagedArgument(int arg) return arg; } - public System.Reflection.Missing ReturnToManaged_Missing() + public string BoolToString() { - return System.Reflection.Missing.Value; + throw new NotImplementedException(); } - public DBNull ReturnToManaged_DBNull() + public void ReturnToManaged_Void(int value) { - return DBNull.Value; + throw new NotImplementedException(); } - public string BoolToString() + public double ReturnToManaged_Double(int value) { throw new NotImplementedException(); } + + public string ReturnToManaged_String(int value) + { + throw new NotImplementedException(); + } + + public decimal ReturnToManaged_Decimal(int value) + { + throw new NotImplementedException(); + } + + public DateTime ReturnToManaged_DateTime(int value) + { + throw new NotImplementedException(); + } + + public System.Drawing.Color ReturnToManaged_Color(int value) + { + throw new NotImplementedException(); + } + + public System.Reflection.Missing ReturnToManaged_Missing(int value) + { + return System.Reflection.Missing.Value; + } + + public DBNull ReturnToManaged_DBNull(int value) + { + return DBNull.Value; + } } \ No newline at end of file diff --git a/src/tests/Interop/COM/NativeClients/Dispatch/Client.cpp b/src/tests/Interop/COM/NativeClients/Dispatch/Client.cpp index 4c509a3df1624..b2286e25b2d03 100644 --- a/src/tests/Interop/COM/NativeClients/Dispatch/Client.cpp +++ b/src/tests/Interop/COM/NativeClients/Dispatch/Client.cpp @@ -462,57 +462,20 @@ void Validate_Enumerator() ValidateReturnedEnumerator(&result); } -void Validate_ParamCoerce_Type(ComSmartPtr& dispatchCoerceTesting, VARENUM type, int lcid, DISPID methodId) +void Validate_ParamCoerce_Success(ComSmartPtr& dispatchCoerceTesting, int lcid, DISPID methodId, VARIANT arg, int expected) { HRESULT hr; DISPPARAMS params; - VARIANTARG arg; + VARIANTARG args; params.cArgs = 1; - params.rgvarg = &arg; + params.rgvarg = &args; params.cNamedArgs = 0; params.rgdispidNamedArgs = nullptr; + args = arg; VARIANT result; - V_VT(&arg) = type; - - switch (type) - { - case VT_BSTR: - { - BSTR str = ::SysAllocString(L"123"); - V_BSTR(&arg) = str; - break; - } - case VT_R4: - { - V_R4(&arg) = 1.23f; - break; - } - case VT_DATE: - case VT_R8: - { - V_R8(&arg) = 1.23; - break; - } - case VT_CY: - { - VarCyFromI4(123, &V_CY(&arg)); - break; - } - case VT_DECIMAL: - { - VarDecFromI4(123, &V_DECIMAL(&arg)); - break; - } - default: - { - V_I1(&arg) = 123; - break; - } - } - THROW_IF_FAILED(dispatchCoerceTesting->Invoke( methodId, IID_NULL, @@ -524,7 +487,35 @@ void Validate_ParamCoerce_Type(ComSmartPtr& dispatchCoer nullptr )); - THROW_FAIL_IF_FALSE(V_I4(&result) != 0); + THROW_FAIL_IF_FALSE(V_I4(&result) == expected); +} + +void Validate_ParamCoerce_Exception(ComSmartPtr& dispatchCoerceTesting, int lcid, DISPID methodId, VARIANT arg, HRESULT expected) +{ + HRESULT hr; + + DISPPARAMS params; + VARIANTARG args; + params.cArgs = 1; + params.rgvarg = &args; + params.cNamedArgs = 0; + params.rgdispidNamedArgs = nullptr; + + args = arg; + VARIANT result; + + hr = dispatchCoerceTesting->Invoke( + methodId, + IID_NULL, + lcid, + DISPATCH_METHOD, + ¶ms, + &result, + nullptr, + nullptr + ); + + THROW_FAIL_IF_FALSE(hr == expected); } void Validate_ParamCoerce() @@ -548,24 +539,68 @@ void Validate_ParamCoerce() lcid, &methodId)); + VARIANT arg; + + ::wprintf(W("Validating VT_UI4\n")); + V_VT(&arg) = VT_UI4; + V_UI4(&arg) = 0x1234ABCD; + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, 0x1234ABCD); + ::wprintf(W("Validating VT_I2\n")); - Validate_ParamCoerce_Type(dispatchCoerceTesting, VT_I2, lcid, methodId); - ::wprintf(W("Validating VT_I4\n")); - Validate_ParamCoerce_Type(dispatchCoerceTesting, VT_I4, lcid, methodId); - ::wprintf(W("Validating VT_R4\n")); - Validate_ParamCoerce_Type(dispatchCoerceTesting, VT_R4, lcid, methodId); + V_VT(&arg) = VT_I2; + V_I2(&arg) = 123; + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, 123); + + ::wprintf(W("Validating VT_I8\n")); + V_VT(&arg) = VT_I8; + V_I8(&arg) = int64_t(1) << 32; + Validate_ParamCoerce_Exception(dispatchCoerceTesting, lcid, methodId, arg, DISP_E_OVERFLOW); + ::wprintf(W("Validating VT_R8\n")); - Validate_ParamCoerce_Type(dispatchCoerceTesting, VT_R8, lcid, methodId); + V_VT(&arg) = VT_R8; + V_R8(&arg) = 123.45; + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, 123); + ::wprintf(W("Validating VT_CY\n")); - Validate_ParamCoerce_Type(dispatchCoerceTesting, VT_CY, lcid, methodId); - ::wprintf(W("Validating VT_DATE\n")); - Validate_ParamCoerce_Type(dispatchCoerceTesting, VT_DATE, lcid, methodId); + V_VT(&arg) = VT_CY; + V_I8(&arg) = 123456; + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, 12); + ::wprintf(W("Validating VT_BSTR\n")); - Validate_ParamCoerce_Type(dispatchCoerceTesting, VT_BSTR, lcid, methodId); - ::wprintf(W("Validating VT_ERROR\n")); - Validate_ParamCoerce_Type(dispatchCoerceTesting, VT_ERROR, lcid, methodId); + V_VT(&arg) = VT_BSTR; + V_BSTR(&arg) = ::SysAllocString(L"123"); + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, 123); + ::wprintf(W("Validating VT_BOOL\n")); - Validate_ParamCoerce_Type(dispatchCoerceTesting, VT_BOOL, lcid, methodId); + V_VT(&arg) = VT_BOOL; + V_BOOL(&arg) = VARIANT_TRUE; + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, -1); + V_VT(&arg) = VT_BOOL; + V_I4(&arg) = 123; + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, -1); + V_VT(&arg) = VT_BOOL; + V_BOOL(&arg) = VARIANT_FALSE; + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, 0); + + ::wprintf(W("Validating VT_DATE\n")); + V_VT(&arg) = VT_DATE; + V_R8(&arg) = -657434.0; + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, -657434); + V_VT(&arg) = VT_DATE; + V_R8(&arg) = -657435.0; + Validate_ParamCoerce_Exception(dispatchCoerceTesting, lcid, methodId, arg, E_INVALIDARG); + V_VT(&arg) = VT_DATE; + V_R8(&arg) = 2958465.0; + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, 2958465); + V_VT(&arg) = VT_DATE; + V_R8(&arg) = 2958466.0; + Validate_ParamCoerce_Exception(dispatchCoerceTesting, lcid, methodId, arg, E_INVALIDARG); + ::wprintf(W("Validating VT_DECIMAL\n")); - Validate_ParamCoerce_Type(dispatchCoerceTesting, VT_DECIMAL, lcid, methodId); + V_VT(&arg) = VT_DECIMAL; + VarDecFromI4(123, &V_DECIMAL(&arg)); + Validate_ParamCoerce_Success(dispatchCoerceTesting, lcid, methodId, arg, 123); + V_VT(&arg) = VT_DECIMAL; + VarDecFromI8(int64_t(1) << 32, &V_DECIMAL(&arg)); + Validate_ParamCoerce_Exception(dispatchCoerceTesting, lcid, methodId, arg, DISP_E_OVERFLOW); } diff --git a/src/tests/Interop/COM/NativeServer/DispatchCoerceTesting.h b/src/tests/Interop/COM/NativeServer/DispatchCoerceTesting.h index 52c6a99b97ad4..f912f7d4bca6c 100644 --- a/src/tests/Interop/COM/NativeServer/DispatchCoerceTesting.h +++ b/src/tests/Interop/COM/NativeServer/DispatchCoerceTesting.h @@ -85,15 +85,19 @@ class DispatchCoerceTesting : public UnknownImpl, public IDispatchCoerceTesting } case 3: { - return ReturnToManaged_Missing_Dispatch(pDispParams, pVarResult); + return BoolToString_Dispatch(pDispParams, pVarResult); } case 4: - { - return ReturnToManaged_DBNull_Dispatch(pDispParams, pVarResult); - } case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: { - return BoolToString_Dispatch(pDispParams, pVarResult); + return ReturnToManaged_Any_Dispatch(pDispParams, pVarResult); } } @@ -209,33 +213,31 @@ class DispatchCoerceTesting : public UnknownImpl, public IDispatchCoerceTesting return S_OK; } - HRESULT ReturnToManaged_Missing_Dispatch(_In_ DISPPARAMS *pDispParams, _Inout_ VARIANT *pVarResult) + HRESULT ReturnToManaged_Any_Dispatch(_In_ DISPPARAMS *pDispParams, _Inout_ VARIANT *pVarResult) { HRESULT hr; - size_t expectedArgCount = 0; + int *args[1]; + size_t expectedArgCount = 1; RETURN_IF_FAILED(VerifyValues(uint32_t(expectedArgCount), pDispParams->cArgs)); if (pVarResult == nullptr) return E_POINTER; - V_VT(pVarResult) = VT_I4; - V_I4(pVarResult) = 1234; - return S_OK; - } - - HRESULT ReturnToManaged_DBNull_Dispatch(_In_ DISPPARAMS *pDispParams, _Inout_ VARIANT *pVarResult) - { - HRESULT hr; - - size_t expectedArgCount = 0; - RETURN_IF_FAILED(VerifyValues(uint32_t(expectedArgCount), pDispParams->cArgs)); + VARENUM currType; + VARIANTARG *currArg; + size_t argIdx = expectedArgCount - 1; - if (pVarResult == nullptr) - return E_POINTER; + // Extract args + { + currType = VT_I4; + currArg = NextArg(pDispParams->rgvarg, argIdx); + RETURN_IF_FAILED(VerifyValues(VARENUM(currType), VARENUM(currArg->vt))); + args[0] = &currArg->intVal; + } V_VT(pVarResult) = VT_I4; - V_I4(pVarResult) = 1234; + V_I4(pVarResult) = *args[0]; return S_OK; } @@ -270,9 +272,15 @@ const WCHAR * const DispatchCoerceTesting::Names[] = W("__RESERVED__"), W("ReturnToManaged"), W("ManagedArgument"), + W("BoolToString"), + W("ReturnToManaged_Void"), + W("ReturnToManaged_Double"), + W("ReturnToManaged_String"), + W("ReturnToManaged_Decimal"), + W("ReturnToManaged_DateTime"), + W("ReturnToManaged_Color"), W("ReturnToManaged_Missing"), W("ReturnToManaged_DBNull"), - W("BoolToString") }; const int DispatchCoerceTesting::NamesCount = ARRAY_SIZE(DispatchCoerceTesting::Names); diff --git a/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs b/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs index 27ee5b3bfc3db..476899377660d 100644 --- a/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs +++ b/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs @@ -325,14 +325,16 @@ public interface TestingEvents public interface IDispatchCoerceTesting { int ReturnToManaged(short vt); - int ManagedArgument(int arg); - - System.Reflection.Missing ReturnToManaged_Missing(); - - DBNull ReturnToManaged_DBNull(); - string BoolToString(); + void ReturnToManaged_Void(int value); + double ReturnToManaged_Double(int value); + string ReturnToManaged_String(int value); + decimal ReturnToManaged_Decimal(int value); + DateTime ReturnToManaged_DateTime(int value); + Color ReturnToManaged_Color(int value); + System.Reflection.Missing ReturnToManaged_Missing(int value); + DBNull ReturnToManaged_DBNull(int value); } [ComVisible(true)]