Skip to content

Commit 56238a5

Browse files
huoyaoyuanAaronRobinsonMSFT
authored andcommitted
Convert OAVariant interop to managed (dotnet#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 <[email protected]>
1 parent 234bb2b commit 56238a5

File tree

20 files changed

+439
-610
lines changed

20 files changed

+439
-610
lines changed

src/coreclr/System.Private.CoreLib/src/Microsoft/Win32/OAVariantLib.cs

Lines changed: 125 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
===========================================================*/
1414

1515
using System;
16+
using System.Collections.Generic;
1617
using System.Diagnostics;
1718
using System.Globalization;
1819
using System.Reflection;
1920
using System.Runtime.CompilerServices;
2021
using System.Runtime.InteropServices;
22+
using System.Runtime.InteropServices.Marshalling;
2123

2224
namespace Microsoft.Win32
2325
{
@@ -26,102 +28,162 @@ internal static unsafe partial class OAVariantLib
2628
#region Constants
2729

2830
// Constants for VariantChangeType from OleAuto.h
29-
public const int NoValueProp = 0x01;
30-
public const int AlphaBool = 0x02;
31-
public const int NoUserOverride = 0x04;
32-
public const int CalendarHijri = 0x08;
3331
public const int LocalBool = 0x10;
3432

35-
internal static readonly Type?[] ClassTypes = {
36-
typeof(Empty),
37-
typeof(void),
38-
typeof(bool),
39-
typeof(char),
40-
typeof(sbyte),
41-
typeof(byte),
42-
typeof(short),
43-
typeof(ushort),
44-
typeof(int),
45-
typeof(uint),
46-
typeof(long),
47-
typeof(ulong),
48-
typeof(float),
49-
typeof(double),
50-
typeof(string),
51-
typeof(void),
52-
typeof(DateTime),
53-
typeof(TimeSpan),
54-
typeof(object),
55-
typeof(decimal),
56-
null, // Enums - what do we do here?
57-
typeof(Missing),
58-
typeof(DBNull),
33+
private static readonly Dictionary<Type, VarEnum> ClassTypes = new Dictionary<Type, VarEnum>
34+
{
35+
{ typeof(bool), VarEnum.VT_BOOL },
36+
{ typeof(char), VarEnum.VT_I2 },
37+
{ typeof(sbyte), VarEnum.VT_I1 },
38+
{ typeof(byte), VarEnum.VT_UI1 },
39+
{ typeof(short), VarEnum.VT_I2 },
40+
{ typeof(ushort), VarEnum.VT_UI2 },
41+
{ typeof(int), VarEnum.VT_I4 },
42+
{ typeof(uint), VarEnum.VT_UI4 },
43+
{ typeof(long), VarEnum.VT_I8 },
44+
{ typeof(ulong), VarEnum.VT_UI8 },
45+
{ typeof(float), VarEnum.VT_R4 },
46+
{ typeof(double), VarEnum.VT_R8 },
47+
{ typeof(string), VarEnum.VT_BSTR },
48+
{ typeof(DateTime), VarEnum.VT_DATE },
49+
{ typeof(decimal), VarEnum.VT_DECIMAL },
5950
};
6051

61-
// Keep these numbers in sync w/ the above array.
62-
private const int CV_OBJECT = 0x12;
63-
6452
#endregion
6553

6654

6755
#region Internal Methods
6856

69-
#pragma warning disable CS8500
70-
7157
/**
7258
* Changes a Variant from one type to another, calling the OLE
7359
* Automation VariantChangeTypeEx routine. Note the legal types here are
7460
* restricted to the subset of what can be legally found in a VB
7561
* Variant and the types that CLR supports explicitly in the
7662
* CLR Variant class.
7763
*/
78-
internal static Variant ChangeType(Variant source, Type targetClass, short options, CultureInfo culture)
64+
internal static object? ChangeType(object source, Type targetClass, short options, CultureInfo culture)
7965
{
8066
ArgumentNullException.ThrowIfNull(targetClass);
8167
ArgumentNullException.ThrowIfNull(culture);
8268

83-
Variant result = default;
84-
ChangeType(
85-
&result,
86-
&source,
87-
culture.LCID,
88-
targetClass.TypeHandle.Value,
89-
GetCVTypeFromClass(targetClass),
90-
options);
91-
return result;
92-
}
69+
object? result = null;
9370

94-
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "OAVariant_ChangeType")]
95-
private static partial void ChangeType(Variant* result, Variant* source, int lcid, IntPtr typeHandle, int cvType, short flags);
71+
if (Variant.IsSystemDrawingColor(targetClass))
72+
{
73+
if (source is int || source is uint)
74+
{
75+
uint sourceData = source is int ? (uint)(int)source : (uint)source;
76+
// Int32/UInt32 can be converted to System.Drawing.Color
77+
Variant.ConvertOleColorToSystemColor(ObjectHandleOnStack.Create(ref result), sourceData, targetClass.TypeHandle.Value);
78+
Debug.Assert(result != null);
79+
return result;
80+
}
81+
}
9682

97-
#pragma warning restore CS8500
83+
if (!ClassTypes.TryGetValue(targetClass, out VarEnum vt))
84+
{
85+
throw new NotSupportedException(SR.NotSupported_ChangeType);
86+
}
9887

99-
#endregion
88+
ComVariant vOp = ToOAVariant(source);
89+
ComVariant ret = default;
10090

91+
int hr = Interop.OleAut32.VariantChangeTypeEx(&ret, &vOp, culture.LCID, options, (ushort)vt);
10192

102-
#region Private Helpers
93+
using (vOp)
94+
using (ret)
95+
{
96+
if (hr < 0)
97+
{
98+
OAFailed(hr);
99+
}
103100

104-
private static int GetCVTypeFromClass(Type ctype)
105-
{
106-
Debug.Assert(ctype != null);
107-
Debug.Assert(ClassTypes[CV_OBJECT] == typeof(object), "OAVariantLib::ClassTypes[CV_OBJECT] == Object.class");
101+
result = FromOAVariant(ret);
102+
if (targetClass == typeof(char))
103+
{
104+
result = (char)(uint)result!;
105+
}
106+
}
108107

109-
// OleAut Binder works better if unrecognized
110-
// types were changed to Object.
111-
int cvtype = CV_OBJECT;
108+
return result;
109+
}
112110

113-
for (int i = 0; i < ClassTypes.Length; i++)
111+
private static void OAFailed(int hr)
112+
{
113+
switch (hr)
114114
{
115-
if (ctype.Equals(ClassTypes[i]))
116-
{
117-
cvtype = i;
118-
break;
119-
}
115+
case HResults.COR_E_OUTOFMEMORY:
116+
throw new OutOfMemoryException();
117+
case HResults.DISP_E_BADVARTYPE:
118+
throw new NotSupportedException(SR.NotSupported_OleAutBadVarType);
119+
case HResults.DISP_E_DIVBYZERO:
120+
throw new DivideByZeroException();
121+
case HResults.DISP_E_OVERFLOW:
122+
throw new OverflowException();
123+
case HResults.DISP_E_TYPEMISMATCH:
124+
throw new InvalidCastException(SR.InvalidCast_OATypeMismatch);
125+
case HResults.E_INVALIDARG:
126+
throw new ArgumentException();
127+
default:
128+
Debug.Fail("Unrecognized HResult - OAVariantLib routine failed in an unexpected way!");
129+
throw Marshal.GetExceptionForHR(hr);
120130
}
131+
}
121132

122-
return cvtype;
133+
private static ComVariant ToOAVariant(object input)
134+
{
135+
return input switch
136+
{
137+
string str => ComVariant.Create(str),
138+
DateTime dateTime => ComVariant.Create(dateTime),
139+
bool b => ComVariant.Create(b),
140+
decimal d => ComVariant.Create(d),
141+
sbyte i1 => ComVariant.Create(i1),
142+
byte u1 => ComVariant.Create(u1),
143+
short i2 => ComVariant.Create(i2),
144+
ushort u2 => ComVariant.Create(u2),
145+
int i4 => ComVariant.Create(i4),
146+
uint u4 => ComVariant.Create(u4),
147+
long i8 => ComVariant.Create(i8),
148+
ulong u8 => ComVariant.Create(u8),
149+
float r4 => ComVariant.Create(r4),
150+
double r8 => ComVariant.Create(r8),
151+
null => default,
152+
Missing => throw new NotSupportedException(SR.NotSupported_ChangeType),
153+
DBNull => ComVariant.Null,
154+
_ => GetComIPFromObjectRef(input) // Convert the object to an IDispatch/IUnknown pointer.
155+
};
156+
}
157+
158+
private static ComVariant GetComIPFromObjectRef(object? obj)
159+
{
160+
IntPtr pUnk = GetIUnknownOrIDispatchForObject(ObjectHandleOnStack.Create(ref obj), out bool isIDispatch);
161+
return ComVariant.CreateRaw(isIDispatch ? VarEnum.VT_DISPATCH : VarEnum.VT_UNKNOWN, pUnk);
123162
}
124163

164+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MarshalNative_GetIUnknownOrIDispatchForObject")]
165+
private static partial IntPtr GetIUnknownOrIDispatchForObject(ObjectHandleOnStack o, [MarshalAs(UnmanagedType.Bool)] out bool isIDispatch);
166+
167+
private static object? FromOAVariant(ComVariant input) =>
168+
input.VarType switch
169+
{
170+
VarEnum.VT_BSTR => input.As<string>(),
171+
VarEnum.VT_DATE => input.As<DateTime>(),
172+
VarEnum.VT_BOOL => input.As<bool>(),
173+
VarEnum.VT_DECIMAL => input.As<decimal>(),
174+
VarEnum.VT_I1 => input.As<sbyte>(),
175+
VarEnum.VT_UI1 => input.As<byte>(),
176+
VarEnum.VT_I2 => input.As<short>(),
177+
VarEnum.VT_UI2 => input.As<ushort>(),
178+
VarEnum.VT_I4 or VarEnum.VT_INT => input.As<int>(),
179+
VarEnum.VT_UI4 or VarEnum.VT_UINT => input.As<uint>(),
180+
VarEnum.VT_I8 => input.As<long>(),
181+
VarEnum.VT_UI8 => input.As<ulong>(),
182+
VarEnum.VT_R4 => input.As<float>(),
183+
VarEnum.VT_R8 => input.As<double>(),
184+
_ => throw new NotSupportedException(SR.NotSupported_ChangeType),
185+
};
186+
125187
#endregion
126188
}
127189
}

src/coreclr/System.Private.CoreLib/src/System/OleAutBinder.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ internal sealed class OleAutBinder : DefaultBinder
1818
// This binder uses OLEAUT to change the type of the variant.
1919
public override object ChangeType(object value, Type type, CultureInfo? cultureInfo)
2020
{
21-
Variant myValue = new Variant(value);
2221
cultureInfo ??= CultureInfo.CurrentCulture;
2322

2423
#if DISPLAY_DEBUG_INFO
@@ -62,7 +61,7 @@ public override object ChangeType(object value, Type type, CultureInfo? cultureI
6261
#endif
6362
// Specify the LocalBool flag to have BOOL values converted to local language rather
6463
// than 0 or -1.
65-
object RetObj = OAVariantLib.ChangeType(myValue, type, OAVariantLib.LocalBool, cultureInfo).ToObject()!;
64+
object RetObj = OAVariantLib.ChangeType(value, type, OAVariantLib.LocalBool, cultureInfo)!;
6665

6766
#if DISPLAY_DEBUG_INFO
6867
Console.WriteLine("Object returned from ChangeType is of type: " + RetObj.GetType().Name);

src/coreclr/System.Private.CoreLib/src/System/Variant.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
namespace System
1919
{
20-
internal struct Variant
20+
internal partial struct Variant
2121
{
2222
// Do Not change the order of these fields.
2323
// They are mapped to the native VariantData * data structure.
@@ -70,6 +70,14 @@ internal struct Variant
7070
internal static Variant Missing => new Variant(CV_MISSING, Type.Missing, 0);
7171
internal static Variant DBNull => new Variant(CV_NULL, System.DBNull.Value, 0);
7272

73+
internal static bool IsSystemDrawingColor(Type type) => type.FullName == "System.Drawing.Color"; // Matches the behavior of IsTypeRefOrDef
74+
75+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Variant_ConvertSystemColorToOleColor")]
76+
internal static partial uint ConvertSystemColorToOleColor(ObjectHandleOnStack obj);
77+
78+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Variant_ConvertOleColorToSystemColor")]
79+
internal static partial void ConvertOleColorToSystemColor(ObjectHandleOnStack objret, uint value, IntPtr pMT);
80+
7381
//
7482
// Native Methods
7583
//

src/coreclr/classlibnative/bcltype/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
22

33
set(BCLTYPE_SOURCES
44
arraynative.cpp
5-
oavariant.cpp
65
objectnative.cpp
76
system.cpp
87
varargsnative.cpp

0 commit comments

Comments
 (0)