Skip to content

Commit 1e50b05

Browse files
[release/8.0-rc2] IDispatch should accept HRESULT as valuetype (#92494)
* IDispatch should accept HRESULT as valuetype This is a regression from .NET Framework. The current behavior has existed since IDispatch was introduced into .NET Core. Added tests for the current behavior. * Ensure vtables line up for early bound. * Add larger comment to workaround. * Comment --------- Co-authored-by: Aaron R Robinson <[email protected]>
1 parent ee89b7c commit 1e50b05

File tree

8 files changed

+67
-3
lines changed

8 files changed

+67
-3
lines changed

src/coreclr/vm/callsiteinspect.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ void CallsiteInspect::PropagateOutParametersBackToCallsite(
433433
*(ARG_SLOT *)(frame->GetReturnValuePtr()) = retVal;
434434
}
435435
#ifdef ENREGISTERED_RETURNTYPE_MAXSIZE
436-
else if (argit.HasNonStandardByvalReturn())
436+
else if (argit.HasNonStandardByvalReturn()
437+
&& !(flags & CallsiteDetails::HResultReturn))
437438
{
438439
// In these cases, put the pointer to the return buffer into
439440
// the frame's return value slot.

src/coreclr/vm/callsiteinspect.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct CallsiteDetails
2525
BeginInvoke = 0x01,
2626
EndInvoke = 0x02,
2727
Ctor = 0x04,
28+
HResultReturn = 0x08,
2829
};
2930
INT32 Flags;
3031
};

src/coreclr/vm/clrtocomcall.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ UINT32 CLRToCOMEventCallWorker(ComPlusMethodFrame* pFrame, ComPlusCallMethodDesc
364364
return 0;
365365
}
366366

367-
CallsiteDetails CreateCallsiteDetails(_In_ FramedMethodFrame *pFrame)
367+
static CallsiteDetails CreateCallsiteDetails(_In_ FramedMethodFrame *pFrame)
368368
{
369369
CONTRACTL
370370
{
@@ -442,10 +442,20 @@ CallsiteDetails CreateCallsiteDetails(_In_ FramedMethodFrame *pFrame)
442442
SigTypeContext::InitTypeContext(pMD, actualType, &typeContext);
443443
}
444444

445+
// If the signature is marked preserve sig, then the return
446+
// is required to be an HRESULT, per COM rules. We set a flag to
447+
// indicate this state to avoid issues when a C# developer defines
448+
// an HRESULT in C# as a ValueClass with a single int field. This
449+
// is convenient but does violate the COM ABI. Setting the flag
450+
// lets us permit this convention and allow either a 4 byte primitive
451+
// or the commonly used C# type "struct HResult { int Value; }".
452+
if (IsMiPreserveSig(pMD->GetImplAttrs()))
453+
callsiteFlags |= CallsiteDetails::HResultReturn;
454+
445455
_ASSERTE(!signature.IsEmpty() && pModule != nullptr);
446456

447457
// Create details
448-
return CallsiteDetails{ { signature, pModule, &typeContext }, pFrame, pMD, fIsDelegate };
458+
return CallsiteDetails{ { signature, pModule, &typeContext }, pFrame, pMD, fIsDelegate, callsiteFlags };
449459
}
450460

451461
UINT32 CLRToCOMLateBoundWorker(

src/tests/Interop/COM/NETClients/IDispatch/Program.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,21 @@ static void Validate_Exception()
139139
Assert.Equal(GetErrorCodeFromHResult(e.HResult), errorCode);
140140
// Failing HRESULT exceptions contain CLR generated messages
141141
}
142+
143+
// Calling methods through IDispatch::Invoke() (i.e., late-bound) doesn't
144+
// propagate the HRESULT when marked with PreserveSig. It is always 0.
145+
{
146+
Console.WriteLine($"Calling {nameof(DispatchTesting.TriggerException)} (PreserveSig) with {nameof(IDispatchTesting_Exception.Int)} {errorCode}...");
147+
var dispatchTesting2 = (IDispatchTestingPreserveSig1)dispatchTesting;
148+
Assert.Equal(0, dispatchTesting2.TriggerException(IDispatchTesting_Exception.Int, errorCode));
149+
}
150+
151+
{
152+
// Validate the HRESULT as a value type construct works for IDispatch.
153+
Console.WriteLine($"Calling {nameof(DispatchTesting.TriggerException)} (PreserveSig, ValueType) with {nameof(IDispatchTesting_Exception.Int)} {errorCode}...");
154+
var dispatchTesting3 = (IDispatchTestingPreserveSig2)dispatchTesting;
155+
Assert.Equal(0, dispatchTesting3.TriggerException(IDispatchTesting_Exception.Int, errorCode).Value);
156+
}
142157
}
143158

144159
static void Validate_StructNotSupported()

src/tests/Interop/COM/NETServer/DispatchTesting.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public void TriggerException(IDispatchTesting_Exception excep, int errorCode)
5757
case IDispatchTesting_Exception.Disp:
5858
throw new Exception();
5959
case IDispatchTesting_Exception.HResult:
60+
case IDispatchTesting_Exception.Int:
6061
throw new System.ComponentModel.Win32Exception(errorCode);
6162
}
6263
}

src/tests/Interop/COM/NativeServer/DispatchTesting.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ class DispatchTesting : public UnknownImpl, public IDispatchTesting
243243
return DISP_E_EXCEPTION;
244244
case IDispatchTesting_Exception_HResult:
245245
return HRESULT_FROM_WIN32(errorCode);
246+
case IDispatchTesting_Exception_Int:
247+
return errorCode;
246248
default:
247249
return S_FALSE; // Return a success case to indicate failure to trigger a failure.
248250
}

src/tests/Interop/COM/ServerContracts/Server.Contracts.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ public enum IDispatchTesting_Exception
209209
{
210210
Disp,
211211
HResult,
212+
Int,
212213
}
213214

214215
[StructLayout(LayoutKind.Sequential)]
@@ -220,6 +221,12 @@ public struct HFA_4
220221
public float w;
221222
}
222223

224+
[StructLayout(LayoutKind.Sequential)]
225+
public struct HRESULT
226+
{
227+
public int Value;
228+
}
229+
223230
[ComVisible(true)]
224231
[Guid("a5e04c1c-474e-46d2-bbc0-769d04e12b54")]
225232
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
@@ -257,6 +264,32 @@ void DoubleNumeric_ReturnByRef (
257264
System.Collections.IEnumerator GetEnumerator();
258265
}
259266

267+
[ComVisible(true)]
268+
[Guid("a5e04c1c-474e-46d2-bbc0-769d04e12b54")]
269+
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
270+
public interface IDispatchTestingPreserveSig1
271+
{
272+
void Reserved1();
273+
void Reserved2();
274+
void Reserved3();
275+
276+
[PreserveSig]
277+
int TriggerException(IDispatchTesting_Exception excep, int errorCode);
278+
}
279+
280+
[ComVisible(true)]
281+
[Guid("a5e04c1c-474e-46d2-bbc0-769d04e12b54")]
282+
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
283+
public interface IDispatchTestingPreserveSig2
284+
{
285+
void Reserved1();
286+
void Reserved2();
287+
void Reserved3();
288+
289+
[PreserveSig]
290+
HRESULT TriggerException(IDispatchTesting_Exception excep, int errorCode);
291+
}
292+
260293
[ComVisible(true)]
261294
[Guid("83AFF8E4-C46A-45DB-9D91-2ADB5164545E")]
262295
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

src/tests/Interop/COM/ServerContracts/Server.Contracts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ enum IDispatchTesting_Exception
385385
{
386386
IDispatchTesting_Exception_Disp,
387387
IDispatchTesting_Exception_HResult,
388+
IDispatchTesting_Exception_Int,
388389
};
389390

390391
struct __declspec(uuid("a5e04c1c-474e-46d2-bbc0-769d04e12b54"))

0 commit comments

Comments
 (0)