Skip to content

Commit 74be414

Browse files
Address COM interop case involving legacy wCode usage. (#117596)
* Address COM interop case involving legacy wCode usage. The wCode field in the EXCEPINFO is a 16-bit signed integer. This is a legacy field for 16-bit Windows, but is still used in many COM servers. The current implementation assumed it would result in an Exception using Marshal.GetExceptionForHR(), but converting a short to an int, will rarely result in a negative value which means null will almost always be returned. This change checks if the exception is null and if so, creates a simple COMException with the error code.
1 parent ede442a commit 74be414

File tree

6 files changed

+47
-13
lines changed

6 files changed

+47
-13
lines changed

src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/ComInterop/ExcepInfo.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ static ExcepInfo()
3333
}
3434
#endif
3535

36-
private static string ConvertAndFreeBstr(ref IntPtr bstr)
36+
private static string? ConvertAndFreeBstr(ref IntPtr bstr)
3737
{
3838
if (bstr == IntPtr.Zero)
3939
{
@@ -55,11 +55,17 @@ internal Exception GetException()
5555
wReserved = -1; // to ensure that the method gets called only once
5656
#endif
5757

58+
// If the scode is zero, we use the wCode. The wCode is a legacy of
59+
// 16-bit Windows error codes. This means it will never be an error
60+
// scode (HRESULT, < 0) and we will get a null exception.
5861
int errorCode = (scode != 0) ? scode : wCode;
59-
Exception exception = Marshal.GetExceptionForHR(errorCode);
6062

61-
string message = ConvertAndFreeBstr(ref bstrDescription);
62-
if (message != null)
63+
// If the error code doesn't resolve to an exception, we create a
64+
// generic COMException with the error code and no message.
65+
Exception exception = Marshal.GetExceptionForHR(errorCode) ?? new COMException(null, errorCode);
66+
67+
string? message = ConvertAndFreeBstr(ref bstrDescription);
68+
if (message is not null)
6369
{
6470
// If we have a custom message, create a new Exception object with the message set correctly.
6571
// We need to create a new object because "exception.Message" is a read-only property.
@@ -71,7 +77,7 @@ internal Exception GetException()
7177
{
7278
Type exceptionType = exception.GetType();
7379
ConstructorInfo ctor = exceptionType.GetConstructor(new Type[] { typeof(string) });
74-
if (ctor != null)
80+
if (ctor is not null)
7581
{
7682
exception = (Exception)ctor.Invoke(new object[] { message });
7783
}
@@ -80,8 +86,8 @@ internal Exception GetException()
8086

8187
exception.Source = ConvertAndFreeBstr(ref bstrSource);
8288

83-
string helpLink = ConvertAndFreeBstr(ref bstrHelpFile);
84-
if (helpLink != null && dwHelpContext != 0)
89+
string? helpLink = ConvertAndFreeBstr(ref bstrHelpFile);
90+
if (helpLink is not null && dwHelpContext != 0)
8591
{
8692
helpLink += "#" + dwHelpContext;
8793
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,15 @@ static void Validate_Double_In_ReturnAndUpdateByRef()
108108

109109
static int GetErrorCodeFromHResult(int hresult)
110110
{
111-
// https://msdn.microsoft.com/en-us/library/cc231198.aspx
111+
// https://msdn.microsoft.com/library/cc231198.aspx
112112
return hresult & 0xffff;
113113
}
114114

115115
static void Validate_Exception()
116116
{
117117
var dispatchTesting = (DispatchTesting)new DispatchTestingClass();
118118

119-
int errorCode = 127;
119+
int errorCode = 1127;
120120
string resultString = errorCode.ToString("x");
121121
try
122122
{
@@ -130,6 +130,19 @@ static void Validate_Exception()
130130
Assert.Equal(e.Message, resultString);
131131
}
132132

133+
try
134+
{
135+
Console.WriteLine($"Calling {nameof(DispatchTesting.TriggerException)} with {nameof(IDispatchTesting_Exception.DispLegacy)} {errorCode}...");
136+
dispatchTesting.TriggerException(IDispatchTesting_Exception.DispLegacy, errorCode);
137+
Assert.Fail("DISP exception not thrown properly");
138+
}
139+
catch (COMException e)
140+
{
141+
Assert.Equal(e.ErrorCode, errorCode); // The legacy DISP exception returns the error code unmodified.
142+
Assert.Equal(e.HResult, errorCode);
143+
Assert.Equal(e.Message, resultString);
144+
}
145+
133146
try
134147
{
135148
Console.WriteLine($"Calling {nameof(DispatchTesting.TriggerException)} with {nameof(IDispatchTesting_Exception.HResult)} {errorCode}...");

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public void TriggerException(IDispatchTesting_Exception excep, int errorCode)
5555
switch (excep)
5656
{
5757
case IDispatchTesting_Exception.Disp:
58+
case IDispatchTesting_Exception.DispLegacy:
5859
throw new Exception();
5960
case IDispatchTesting_Exception.HResult:
6061
case IDispatchTesting_Exception.Int:

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ class DispatchTesting : public UnknownImpl, public IDispatchTesting
240240
switch (excep)
241241
{
242242
case IDispatchTesting_Exception_Disp:
243+
case IDispatchTesting_Exception_Disp_Legacy:
243244
return DISP_E_EXCEPTION;
244245
case IDispatchTesting_Exception_HResult:
245246
return HRESULT_FROM_WIN32(errorCode);
@@ -457,11 +458,22 @@ class DispatchTesting : public UnknownImpl, public IDispatchTesting
457458
args[1] = &currArg->intVal;
458459
}
459460

460-
hr = TriggerException(static_cast<IDispatchTesting_Exception>(*args[0]), *args[1]);
461+
IDispatchTesting_Exception kind = static_cast<IDispatchTesting_Exception>(*args[0]);
462+
hr = TriggerException(kind, *args[1]);
461463
if (hr == DISP_E_EXCEPTION)
462464
{
463465
*puArgErr = 1;
464-
pExcepInfo->scode = HRESULT_FROM_WIN32(*args[1]);
466+
if (kind == IDispatchTesting_Exception_Disp_Legacy)
467+
{
468+
pExcepInfo->wCode = *args[1]; // Legacy exception code
469+
pExcepInfo->scode = 0;
470+
}
471+
else
472+
{
473+
assert(kind == IDispatchTesting_Exception_Disp);
474+
pExcepInfo->wCode = 0;
475+
pExcepInfo->scode = HRESULT_FROM_WIN32(*args[1]);
476+
}
465477

466478
WCHAR buffer[ARRAY_SIZE(W("4294967295"))];
467479
_snwprintf_s(buffer, ARRAY_SIZE(buffer), _TRUNCATE, W("%x"), *args[1]);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ public interface IErrorMarshalTesting
237237

238238
public enum IDispatchTesting_Exception
239239
{
240-
Disp,
240+
Disp, // scode
241+
DispLegacy, // wCode
241242
HResult,
242243
Int,
243244
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,8 @@ IErrorMarshalTesting : IUnknown
413413

414414
enum IDispatchTesting_Exception
415415
{
416-
IDispatchTesting_Exception_Disp,
416+
IDispatchTesting_Exception_Disp, // scode
417+
IDispatchTesting_Exception_Disp_Legacy, // wCode
417418
IDispatchTesting_Exception_HResult,
418419
IDispatchTesting_Exception_Int,
419420
};

0 commit comments

Comments
 (0)