WPF 已知问题 包含 NaN 的 Geometry 几何可能导致渲染层抛出 UCEERR_RENDERTHREADFAILURE 异常

news/2024/9/18 5:12:47/文章来源:https://www.cnblogs.com/lindexi/p/17897881.html

本文记录一个 WPF 已知问题,当传入到渲染的 Geometry 几何里面包含了 NaN 数值,将可能让应用程序收到从渲染层抛上来的 UCEERR_RENDERTHREADFAILURE 异常,且此异常缺乏必要信息,比较难定位到具体错误逻辑

此问题是小伙伴报告给我的,详细请看 https://github.com/dotnet/wpf/issues/7421

此问题仅仅只发生在比较特殊的情况,其他情况下都能比较及时收到异常或者是 WPF 层会吞没异常,忽略 Geometry 几何的行为,就当成此 Geometry 几何不存在。由于在所有逻辑里面提前判断参数的合法将降低通用逻辑性能,因此我决定了此问题不做修复,仅仅只是调查问题的原因

我将此问题的原因记录到问题的 Issues 上,同步也写了本文内容

复现步骤稍微复杂,复现代码如下

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;namespace NaN_Crash
{internal class Class1 : Control{protected override void OnRender(DrawingContext dc){var rc = new Rect(0, 0, ActualWidth, ActualHeight);// bad rcrc = new Rect(0, double.NaN, 36, 144);Geometry.DrawRoundedRect(dc, rc, 18);}}internal static class Geometry{static void Curve(PathFigure pf, double x, double y, double cr, bool clockwise){pf.Segments.Add(new ArcSegment(new Point(x, y),new Size(cr, cr),0,false,clockwise ? SweepDirection.Clockwise : SweepDirection.Counterclockwise,true));}static void Line(PathFigure pf, double x, double y){pf.Segments.Add(new LineSegment(new Point(x, y), true));}public static PathGeometry GetRoundedRect(Rect rect, double cr){PathGeometry pg = new PathGeometry();PathFigure pf = new PathFigure();pf.StartPoint = new Point(rect.Left + cr, rect.Top);Line(pf, rect.Right - cr, rect.Top);Curve(pf, rect.Right, rect.Top + cr, cr, true);Line(pf, rect.Right, rect.Bottom - cr);Curve(pf, rect.Right - cr, rect.Bottom, cr, true);Line(pf, rect.Left + cr, rect.Bottom);Curve(pf, rect.Left, rect.Bottom - cr, cr, true);Line(pf, rect.Left, rect.Top + cr);Curve(pf, rect.Left + cr, rect.Top, cr, true);pf.IsClosed = true;pf.Freeze();pg.Figures.Add(pf);pg.Freeze();return pg;}public struct InnerGeometryInfo{public System.Windows.Media.Geometry Geo { get; set; }public double InnerBr { get; set; }public Rect InnerRect { get; set; }}public static InnerGeometryInfo GetInnerGeo(Rect rect, double br){var th = new Thickness(1, 1, 1, 1);var innerRect = Adjust(rect, th);double innerBr = Math.Max(0, br - 1.0);return new InnerGeometryInfo(){Geo = GetRoundedRect(innerRect, innerBr),InnerBr = innerBr,InnerRect = innerRect};}public static void DrawRoundedRect(DrawingContext dc, Rect rect, double br){var geo1 = GetRoundedRect(rect, br);var innerInfo = GetInnerGeo(rect, br);var innerBr = innerInfo.InnerBr;var innerRect = Adjust(rect, new Thickness(1.0));var geo = new CombinedGeometry(GeometryCombineMode.Exclude, geo1,innerInfo.Geo);dc.PushClip(geo);dc.Pop();}public static Rect SafeRect(double x, double y, double w, double h){return new Rect(x, y, Math.Max(0, w), Math.Max(0, h));}public static Rect Adjust(Rect rc, Thickness? th){if (th.HasValue){return SafeRect(rc.Left + th.Value.Left, rc.Top + th.Value.Top, rc.Width - th.Value.Left - th.Value.Right, rc.Height - th.Value.Top - th.Value.Bottom);}return rc;}}
}

将此 Class1 放入到 MainWindow.xaml 里面,运行项目即可复现问题

可以看到抛出的异常如下

An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in PresentationCore.dll
UCEERR_RENDERTHREADFAILURE (0x88980406)PresentationCore.dll!System.Windows.Media.Composition.DUCE.Channel.SyncFlush()	UnknownPresentationCore.dll!System.Windows.Interop.HwndTarget.UpdateWindowSettings(bool enableRenderTarget, System.Windows.Media.Composition.DUCE.ChannelSet? channelSet)	UnknownPresentationCore.dll!System.Windows.Interop.HwndTarget.UpdateWindowPos(System.IntPtr lParam)	UnknownPresentationCore.dll!System.Windows.Interop.HwndTarget.HandleMessage(MS.Internal.Interop.WindowMessage msg, System.IntPtr wparam, System.IntPtr lparam)	UnknownPresentationCore.dll!System.Windows.Interop.HwndSource.HwndTargetFilterMessage(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled)	UnknownWindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled)	UnknownWindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o)	UnknownWindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs)	UnknownWindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler)	UnknownWindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs)	UnknownWindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam)	Unknown[Native to Managed Transition]	[Managed to Native Transition]	PresentationFramework.dll!System.Windows.Window.Flush()	UnknownPresentationFramework.dll!System.Windows.Window.HwndStyleManager.System.IDisposable.Dispose()	UnknownPresentationFramework.dll!System.Windows.Window.CreateSourceWindow(bool duringShow)	UnknownPresentationFramework.dll!System.Windows.Window.CreateSourceWindowDuringShow()	UnknownPresentationFramework.dll!System.Windows.Window.SafeCreateWindowDuringShow()	UnknownPresentationFramework.dll!System.Windows.Window.ShowHelper(object booleanBox)	UnknownPresentationFramework.dll!System.Windows.Window.Show()	UnknownMicrosoft.VisualStudio.DesignTools.WpfTap.dll!Microsoft.VisualStudio.DesignTools.WpfTap.WpfVisualTreeService.Adorners.AdornerWindow.SafeShow()	UnknownMicrosoft.VisualStudio.DesignTools.WpfTap.dll!Microsoft.VisualStudio.DesignTools.WpfTap.WpfVisualTreeService.Adorners.AdornerWindow.UpdatePlacement()	UnknownMicrosoft.VisualStudio.DesignTools.WpfTap.dll!Microsoft.VisualStudio.DesignTools.WpfTap.Utility.DispatcherUtility.SafeInvokeAsync.AnonymousMethod__0()	UnknownWindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeDelegateCore()	UnknownWindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl()	UnknownWindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(object state)	UnknownWindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(object obj)	UnknownSystem.Private.CoreLib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)	UnknownSystem.Private.CoreLib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)	UnknownWindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext executionContext, System.Threading.ContextCallback callback, object state)	UnknownWindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke()	UnknownWindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue()	UnknownWindowsBase.dll!System.Windows.Threading.Dispatcher.WndProcHook(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled)	UnknownWindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled)	UnknownWindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o)	UnknownWindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs)	UnknownWindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler)	UnknownWindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs)	UnknownWindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam)	Unknown[Native to Managed Transition]	[Managed to Native Transition]	WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame)	UnknownWindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame)	UnknownWindowsBase.dll!System.Windows.Threading.Dispatcher.Run()	UnknownPresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore)	UnknownPresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window)	UnknownPresentationFramework.dll!System.Windows.Application.Run()	UnknownNaN-Crash.dll!NaN_Crash.App.Main()	Unknown

这个异常存在的问题是缺乏足够的提示信息,导致难以定位具体问题。尽管可以在 CombinedGeometry 进行拦截,且当前的 CombinedGeometry 已经做了一定的拦截措施,但如果想要进行更进一步的拦截本问题,则需要修改到通用逻辑。修改通用逻辑将会降低通用逻辑性能。由于此问题比较难以复现,即使出现问题了,慢慢调试也能找到坑。于是我就决定此问题不修复,但是我将会记录下来出现此问题的原因

我通过调试 WPF 框架,调试 WPF 的 GFX 层调试到问题的原因。核心原因是在 CShapeBase::GetCachedBounds 对 Geometry 的 Bounds 进行校验失败,因此导致此方法返回了 WGXERR_BADNUMBER 错误。此 WGXERR_BADNUMBER 错误对应 0x8898000a 错误码,也就是在调试下可以看到 MIL 层的如下输出

MIL FAILURE: Unexpected HRESULT 0x8898000a in caller: The render thread failed unexpectedly.

根据的是在 CShapeBase::GetCachedBoundsif (!rect.HasValidValues()) 判断不通过,详细请看以下代码

 HRESULT CShapeBase::GetCachedBounds( __out_ecount(1) CMilRectF &rect) const     // The data's cached bounds { HRESULT hr = S_OK; if (!GetCachedBoundsCore(rect)) { // Compute the bounds and update the cache CMilRectF box; IFC(GetFillBounds(OUT box, false /* including non-fillable figures */)); SetCachedBounds(box); rect = box; } if (!rect.HasValidValues()) { IFC(WGXERR_BADNUMBER); } Cleanup: RRETURN(hr); } 

为什么以上代码的 HasValidValues 方法将无法通过?原因是这个 rect 包含了 NaN 的内容。那为什么这个 rect 包含了 NaN 的内容,是在哪一层投毒的

如上面代码,在 CShapeBase::GetFillBounds 方法里面就获取到了不合法的 box 值。接下来我将细细告诉大家这个调用链是如何一步步使用错误的上层业务代码传入的 Geometry 数据,获取到错误的参数。在 CShapeBase::GetFillBounds 方法里将会调用到 CShapeBase::UpdateBounds 方法进行更新 Bounds 范围,而 UpdateBounds 方法将返回包含 NaN 的 Bounds 范围

在 WPF 底层里面,许多代码都使用 Shape 来表示 Geometry 几何

以下是 CShapeBase::UpdateBounds 方法的代码

 CShapeBase::UpdateBounds( __inout_ecount(1) CBounds &bounds, // Bounds, updated here __in bool fFillOnly, // Skip non-fillable figures if true __in_ecount_opt(1) const CMILMatrix *pMatrix // Transformation (NULL OK) ) const { HRESULT hr = S_OK; for (UINT i = 0;  i < GetFigureCount();  i++) { const IFigureData & figure = GetFigure(i); if (!fFillOnly || figure.IsFillable()) { IFC(CFigureBase(figure).UpdateBounds(OUT bounds, pMatrix)); } } Cleanup: RRETURN(hr); } 

CShapeBase::UpdateBounds 里面,将会使用每个 Figure 进行更新 Bounds 范围内容,代码如下

HRESULT
CFigureBase::UpdateBounds(__inout_ecount(1) CBounds &bounds,// In/out: Bounds, updated here__in_ecount_opt(1) const CMILMatrix *pMatrix// In: Transformation (NULL OK)) const
{HRESULT hr = S_OK;if (!m_refData.IsEmpty()){if (pMatrix == NULL &&m_refData.IsAxisAlignedRectangle()){MilPoint2F ptCorners[2];m_refData.GetRectangleCorners(ptCorners);bounds.UpdateWithPoint(ptCorners[0]);bounds.UpdateWithPoint(ptCorners[1]);}else{CBoundsTask task(bounds, m_refData.GetStartPoint(), pMatrix);IFC(task.TraverseForward(m_refData));}}Cleanup:RRETURN(hr);
}

在本文的例子代码里面,将会进入 IFC(task.TraverseForward(m_refData)) 分支

task.TraverseForward(m_refData) 里面,将会获取每段的内容更新 Bounds 范围

HRESULT
CFigureTask::TraverseForward(__in_ecount(1) const IFigureData &figure// The traversed figure)
{HRESULT hr = S_OK;const MilPoint2F *pPt;BYTE bType;m_fAborted = false;if (!figure.SetToFirstSegment())goto Cleanup;do {figure.GetCurrentSegment(bType, pPt);if (MilCoreSeg::TypeLine == bType){          IFC(DoLine(*pPt));}else{Assert(MilCoreSeg::TypeBezier == bType);IFC(DoBezier(pPt));}} while (!m_fAborted  &&  figure.SetToNextSegment());Cleanup:RRETURN(hr);
}

本文这里将进入 DoLine(*pPt) 分支,在进入 DoLine 时拿到的 pPt 已经是错误的值了。在 figure.GetCurrentSegment 里面将会调用到 GetStartPoint 方法,这就是本文代码例子里面最开始投毒的地方,后续也会在 DoBezier 里面继续投毒。先看一下最开始投毒的逻辑,在 PathGeometryWrapper.cpp 定义的 GetStartPoint 方法,返回了本文使用的代码里面传入的包含 NaN 的点的值,如以下代码,拿到的 m_pFigure->StartPoint 的值是不符合预期的 {X=18.000000000000000 Y=-nan(ind) }

 const MilPoint2F &PathFigureData::GetStartPoint() const { Assert(m_pFigure != NULL); m_ptStartPoint = ConvertToSingle(m_pFigure->StartPoint); if (m_pMatrix != NULL) { TransformPoint(*m_pMatrix, m_ptStartPoint); } return m_ptStartPoint; } 

这也就导致 CFigureTask::TraverseForward 通过 m_oBounds.UpdateWithPoint(m_ptCurrent) 这行代码从 StartPoint 里面将 NaN 带入到 bounds 里面

在 UpdateWithPoint 方法里面将会调用到 CBounds::UpdateNaN 设置 NaN 到 m_fEncounteredNaN 字段,如以下代码

 void UpdateNaN(GpPointR pt) { m_fEncounteredNaN = m_fEncounteredNaN || _isnan(pt.X) || _isnan(pt.Y); } 

于是这个 Bounds 范围给到 rect 变量,将让 HasValidValues 方法不通过,返回异常。进入到以上的 UpdateNaN 方法的堆栈如下

 	wpfgfx_cor3.dll!CBounds::UpdateNaN(GpPointR pt={...}) Line 526	C++wpfgfx_cor3.dll!CBounds::UpdateWithPoint(const GpPointR & pt={...}) Line 761	C++wpfgfx_cor3.dll!CBoundsTask::DoLineNoHRESULT(const MilPoint2F & ptEnd={...}) Line 58	C++wpfgfx_cor3.dll!CBoundsTask::DoLine(const MilPoint2F & ptEnd={...}) Line 211	C++wpfgfx_cor3.dll!CFigureTask::TraverseForward(const IFigureData & figure={...}) Line 531	C++wpfgfx_cor3.dll!CFigureBase::UpdateBounds(CBounds & bounds={...}, const CMILMatrix * pMatrix=0x00000000) Line 62	C++wpfgfx_cor3.dll!CShapeBase::UpdateBounds(CBounds & bounds={...}, bool fFillOnly=false, const CMILMatrix * pMatrix=0x00000000) Line 1440	C++wpfgfx_cor3.dll!CShapeBase::GetFillBounds(TMilRect_<float,MilRectF,MilPointAndSizeF,RectUniqueness::NotNeeded> & rect={...}, bool fFillOnly=false, const CMILMatrix * pMatrix=0x00000000) Line 1407	C++wpfgfx_cor3.dll!CShapeBase::GetCachedBounds(TMilRect_<float,MilRectF,MilPointAndSizeF,RectUniqueness::NotNeeded> & rect={...}) Line 1534	C++wpfgfx_cor3.dll!CShapeBase::GetTightBounds(TMilRect_<float,MilRectF,MilPointAndSizeF,RectUniqueness::NotNeeded> & rect={...}, const CPlainPen * pPen=0x00000000, const CMILMatrix * pMatrix=0x00000000, double rTolerance=0.0000000000000000, bool fRelative=false, bool fSkipHollows=true) Line 1356	C++wpfgfx_cor3.dll!CShapeBase::Combine(const CShapeBase * pFirst=0x0e4b4510, const CShapeBase * pSecond=0x0e4b4ab0, MilCombineMode::Enum eOperation=Exclude, bool fRetrieveCurves=true, CShape * pResult=0x129b2cec, const CMILMatrix * pFirstTransform=0x00000000, const CMILMatrix * pSecondTransform=0x00000000, double rTolerance=0.25000000000000000, bool fRelative=false) Line 700	C++wpfgfx_cor3.dll!CMilCombinedGeometryDuce::GetShapeDataCore(CShapeBase * * ppShapeData=0x129b2cdc) Line 106	C++wpfgfx_cor3.dll!CMilGeometryDuce::GetShapeData(CShapeBase * * ppShapeData=0x0f37f454) Line 98	C++wpfgfx_cor3.dll!CDrawingContext::PushEffects(const double & rOpacity=1.0000000000000000, CMilGeometryDuce * pGeometryMask=0x129b2cc8, CMilBrushDuce * pOpacityMaskBrush=0x00000000, CMilEffectDuce * pEffect=0x00000000, const CRectF<CoordinateSpace::LocalRenderingHPC> * pSurfaceBoundsLocalSpace=0x00000000) Line 2689	C++wpfgfx_cor3.dll!CDrawingContext::PushClip(CMilGeometryDuce * pClipGeometry=0x129b2cc8) Line 2076	C++wpfgfx_cor3.dll!CMilSlaveRenderData::Draw(IDrawingContext * pIDC=0x129f9bc8) Line 1025	C++wpfgfx_cor3.dll!CContentBounder::GetContentBounds(CMilSlaveResource * pContent=0x0e5d7ed0, TMilRect_<float,MilRectF,MilPointAndSizeF,RectUniqueness::NotNeeded> * prcBounds=0x129f7f90) Line 196	C++wpfgfx_cor3.dll!CMilVisual::GetContentBounds(CContentBounder * pContentBounder=0x12883d58, TMilRect_<float,MilRectF,MilPointAndSizeF,RectUniqueness::NotNeeded> * prcBounds=0x129f7f90) Line 1176	C++wpfgfx_cor3.dll!CPreComputeContext::PreSubgraph(int * pfVisitChildren=0x0f37f828) Line 319	C++wpfgfx_cor3.dll!CGraphIterator::Walk(IGraphNode * pRoot=0x0e6d96d8, IGraphIteratorSink * pSink=0x129b6208) Line 308	C++wpfgfx_cor3.dll!CPreComputeContext::PreCompute(CMilVisual * pRoot=0x0e6d96d8, const TMilRect_<float,MilRectF,MilPointAndSizeF,RectUniqueness::NotNeeded> * prcSurfaceBounds=0x0f37f9f4, unsigned int uNumInvalidTargetRegions=0, const MilRectF * rgInvalidTargetRegions=0x0501e750, float allowedDirtyRegionOverhead=50000.0000, MilBitmapInterpolationMode::Enum defaultInterpolationMode=Linear, ScrollAreaStruct * pScrollArea=0x00000000, int fDisableDirtyRegionOptimization=1) Line 136	C++wpfgfx_cor3.dll!CDrawingContext::PreCompute(CMilVisual * pRoot=0x0e6d96d8, const TMilRect_<float,MilRectF,MilPointAndSizeF,RectUniqueness::NotNeeded> * prcSurfaceBounds=0x0f37f9f4, unsigned int uNumInvalidTargetRegions=0, const MilRectF * rgInvalidTargetRegions=0x0501e750, float allowedDirtyRegionOverhead=50000.0000, int fFullRender=1, ScrollAreaStruct * pScrollArea=0x00000000) Line 5681	C++wpfgfx_cor3.dll!CDrawingContext::Render(CMilVisual * pRoot=0x0e6d96d8, IMILRenderTarget * pIRenderTarget=0x0501e64c, const _D3DCOLORVALUE * pClearColor=0x0d122818, const TMilRect_<float,MilRectF,MilPointAndSizeF,RectUniqueness::NotNeeded> & rcSurfaceBounds={...}, int fFullRender=1, unsigned int uNumInvalidTargetRegions=0, const MilRectF * rgInvalidTargetRegions=0x0501e750, bool fCanAccelerateScroll=false, int * pfNeedsFullPresent=0x0f37fa08) Line 5944	C++wpfgfx_cor3.dll!CSlaveHWndRenderTarget::Render(bool * pfNeedsPresent=0x0f37fa4f) Line 185	C++wpfgfx_cor3.dll!CRenderTargetManager::Render(bool * pfPresentNeeded=0x0f37fb23) Line 339	C++wpfgfx_cor3.dll!CComposition::Render(bool * pfPresentNeeded=0x0f37fb23) Line 859	C++wpfgfx_cor3.dll!CComposition::ProcessComposition(bool * pfPresentNeeded=0x0f37fb23) Line 712	C++wpfgfx_cor3.dll!CComposition::Compose(bool * pfPresentNeeded=0x0f37fb3f) Line 805	C++wpfgfx_cor3.dll!CPartitionThread::RenderPartition(Partition * pPartition=0x0d0b8ac0) Line 134	C++wpfgfx_cor3.dll!CPartitionThread::Run() Line 235	C++wpfgfx_cor3.dll!CPartitionThread::ThreadMain(void * pv=0x0b99b5d0) Line 46	C++kernel32.dll!@BaseThreadInitThunk@12()ntdll.dll!___RtlUserThreadStart@8()ntdll.dll!__RtlUserThreadStart@8()

其他的投毒逻辑也差不多,只需要在 figure 拿到的点包含 NaN 即可更新到 Bounds 导致拿到不符合预期的内容

那为什么上层收到的是 RENDERTHREADFAILURE 异常而不是这里的 WGXERR_BADNUMBER 错误码?这是因为在 wpfgfx_cor3.dll!CPartitionThread::RenderPartition(Partition * pPartition=0x0d0b8ac0) Line 134 C++ 这行代码里面,也就是以上堆栈的倒数第六行,将会统一处理异常

CPartitionThread::RenderPartition 处于 GFX 的上层了,在这里将会进入 CPartitionManager::ZombifyPartitionAndCompleteProcessing 方法进行处理异常,调用堆栈如下

 	wpfgfx_cor3.dll!CPartitionManager::ZombifyPartitionAndCompleteProcessing(Partition * pPartition=0x0ce18aa0, HRESULT hrFailureCode=0x8898000a) Line 540	C++wpfgfx_cor3.dll!CPartitionThread::RenderPartition(Partition * pPartition=0x0ce18aa0) Line 144	C++wpfgfx_cor3.dll!CPartitionThread::Run() Line 235	C++wpfgfx_cor3.dll!CPartitionThread::ThreadMain(void * pv=0x0bdab2b8) Line 46	C++kernel32.dll!@BaseThreadInitThunk@12()ntdll.dll!___RtlUserThreadStart@8()ntdll.dll!__RtlUserThreadStart@8()

CPartitionManager::ZombifyPartitionAndCompleteProcessing 里面,只是判断 hrFailureCode 是不是 D3DERR_OUTOFVIDEOMEMORY 这个 D3D 设备的内存不足或者是其他内存不足问题,如果不是这两个问题,则替换通用渲染异常

void
CPartitionManager::ZombifyPartitionAndCompleteProcessing(__in_ecount(1) Partition *pPartition,HRESULT hrFailureCode)
{// This routine should be called from worker thread.Assert(CurrentThreadIsWorkerThread());//// We only return OOM or OOVM back to through the back channel.// For all other failures, just return a generic render thread failure// failure since we don't want to give out the details// for security reasons.//if (hrFailureCode == D3DERR_OUTOFVIDEOMEMORY){pPartition->m_hrZombieNotificationFailureReason = D3DERR_OUTOFVIDEOMEMORY;}else if (IsOOM(hrFailureCode)){pPartition->m_hrZombieNotificationFailureReason = E_OUTOFMEMORY;}else{//// Note: This failure is not as a result in a problem in this code, something//       happened in the render thread that resulted in us zombifying the //       partition. Look at stack backtrace capture to determine the root//       cause of the failure.// MilUnexpectedError(hrFailureCode, TEXT("The render thread failed unexpectedly."));pPartition->m_hrZombieNotificationFailureReason = WGXERR_UCE_RENDERTHREADFAILURE;}SetPartitionState(pPartition,PartitionZombifyClearFlags,PartitionZombifySetFlags);
}

至于为什么要替换为通用异常,如以上注释 For all other failures, just return a generic render thread failure since we don't want to give out the details for security reasons. 这是为了安全,虽然我也不知道为什么将具体的错误信息返回上层就不安全了

通用处理返回的 WGXERR_UCE_RENDERTHREADFAILURE 错误,将会在 C# 层抛出 UCEERR_RENDERTHREADFAILURE (0x88980406) 异常

这看起来在 GFX 层的处理也是合理的

在 WPF 的底层里面,已经对此做了许多兼容逻辑,比如另一个会进入到 WGXERR_BADNUMBER 错误码的地方是在 PathGeometry.InternalCombine 堆栈里面,如以下代码

 if (hr == (int)MILErrors.WGXERR_BADNUMBER) { // When we encounter NaNs in the renderer, we absorb the error and draw // nothing. To be consistent, we return an empty geometry. resultGeometry = new PathGeometry(); } 

也就是说在 PathGeometry 底层已经判断了此情况,如果有 NaN 的情况就替换为空的 PathGeometry 对象。在本文的例子代码里,其实也进入了这个分支,调用堆栈如下

 	wpfgfx_cor3.dll!CShapeBase::GetCachedBounds(TMilRect_<float,MilRectF,MilPointAndSizeF,RectUniqueness::NotNeeded> & rect={...}) Line 1541	C++wpfgfx_cor3.dll!CShapeBase::GetTightBounds(TMilRect_<float,MilRectF,MilPointAndSizeF,RectUniqueness::NotNeeded> & rect={...}, const CPlainPen * pPen=0x00000000, const CMILMatrix * pMatrix=0x00000000, double rTolerance=0.0000000000000000, bool fRelative=false, bool fSkipHollows=true) Line 1356	C++wpfgfx_cor3.dll!CShapeBase::Combine(const CShapeBase * pFirst=0x006bae44, const CShapeBase * pSecond=0x006baf00, MilCombineMode::Enum eOperation=Exclude, bool fRetrieveCurves=true, CShape * pResult=0x006bafbc, const CMILMatrix * pFirstTransform=0x00000000, const CMILMatrix * pSecondTransform=0x00000000, double rTolerance=0.25000000000000000, bool fRelative=false) Line 700	C++wpfgfx_cor3.dll!MilUtility_PathGeometryCombine(_MilMatrix3x2D * pGeometryMatrix=0x006bb424, _MilMatrix3x2D * pMatrix1=0x006bb3f4, MilFillMode::Enum fillRule1=Alternate, MilPathGeometry * pPathData1=0x054c66c4, unsigned int nSize1=472, _MilMatrix3x2D * pMatrix2=0x006bb3bc, MilFillMode::Enum fillRule2=Alternate, MilPathGeometry * pPathData2=0x054c694c, unsigned int nSize2=472, double rTolerance=0.25000000000000000, bool fRelative=false, void(__stdcall*)(int, int, MilPoint2F *, unsigned int, unsigned char *, unsigned int) fnAddFigureToList=0x04b7377a, MilCombineMode::Enum combineMode=Exclude, MilFillMode::Enum * pOutFillRule=0x006bb3a0) Line 386	C++PresentationCore.dll!System.Windows.Media.PathGeometry.InternalCombine(System.Windows.Media.Geometry geometry1 = {System.Windows.Media.PathGeometry}, System.Windows.Media.Geometry geometry2 = {System.Windows.Media.PathGeometry}, System.Windows.Media.GeometryCombineMode mode = Exclude, System.Windows.Media.Transform transform = {System.Windows.Media.MatrixTransform}, double tolerance = 0.25, System.Windows.Media.ToleranceType type = Absolute) Line 661	C#PresentationCore.dll!System.Windows.Media.Geometry.Combine(System.Windows.Media.Geometry geometry1 = {System.Windows.Media.PathGeometry}, System.Windows.Media.Geometry geometry2 = {System.Windows.Media.PathGeometry}, System.Windows.Media.GeometryCombineMode mode = Exclude, System.Windows.Media.Transform transform = {System.Windows.Media.MatrixTransform}) Line 814	C#PresentationCore.dll!System.Windows.Media.CombinedGeometry.GetAsPathGeometry() Line 275	C#PresentationCore.dll!System.Windows.Media.CombinedGeometry.GetBoundsInternal(System.Windows.Media.Pen pen = null, System.Windows.Media.Matrix matrix = {System.Windows.Media.Matrix}, double tolerance = 0.25, System.Windows.Media.ToleranceType type = Absolute) Line 146	C#PresentationCore.dll!System.Windows.Media.Geometry.GetBoundsInternal(System.Windows.Media.Pen pen = null, System.Windows.Media.Matrix matrix = {System.Windows.Media.Matrix}) Line 161	C#PresentationCore.dll!System.Windows.Media.BoundsDrawingContextWalker.PushClip(System.Windows.Media.Geometry clipGeometry = {System.Windows.Media.CombinedGeometry}) Line 375	C#PresentationCore.dll!System.Windows.Media.RenderData.DrawingContextWalk(System.Windows.Media.DrawingContextWalker ctx = {System.Windows.Media.BoundsDrawingContextWalker}) Line 1336	C#PresentationCore.dll!System.Windows.Media.RenderData.GetContentBounds(System.Windows.Media.BoundsDrawingContextWalker ctx = {System.Windows.Media.BoundsDrawingContextWalker}) Line 139	C#PresentationCore.dll!System.Windows.UIElement.GetHitTestBounds() Line 1335	C#

也就是 CombinedGeometry 的底层也是通过 PathGeometry.InternalCombine 实现合并,然而以上的代码仅仅只是用在获取 Bounds 范围,而没有更进一步给到渲染层

但从这里也可以看到,只有很少的路径才能触发此问题,一般都能进入 WPF 的兼容处理逻辑

这也就是我决定不修复此问题的原因

本文的调试方法就是将 WPF 仓库拉下来,然后构建,构建方法请参阅 手把手教你如何构建 WPF 官方开源框架源代码

然后修改 csproj 文件,请将下面的 C:\lindexi\Code\WPF 替换为你的 WPF 文件夹

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>WinExe</OutputType><TargetFramework>net6.0-windows</TargetFramework><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings><UseWPF>true</UseWPF><Platforms>x86</Platforms></PropertyGroup><PropertyGroup><!-- Change this value based on where your local repo is located --><WpfRepoRoot>C:\lindexi\Code\WPF</WpfRepoRoot><!-- Change based on which assemblies you build (Release/Debug) --><WpfConfig>Debug</WpfConfig><WpfOuputFolder>Microsoft.DotNet.Wpf.GitHub.Debug</WpfOuputFolder><!-- Publishing a self-contained app ensures our binaries are used. --><SelfContained>true</SelfContained><!-- The runtime identifier needs to match the architecture you built WPF assemblies for. --><RuntimeIdentifier>win-x86</RuntimeIdentifier></PropertyGroup><ItemGroup><Reference Include="$(WpfRepoRoot)\artifacts\packaging\$(WpfConfig)\$(WpfOuputFolder)\lib\net6.0\*.dll" /><ReferenceCopyLocalPaths Include="$(WpfRepoRoot)\artifacts\packaging\$(WpfConfig)\$(WpfOuputFolder)\lib\$(RuntimeIdentifier)\*.dll" /><ReferenceCopyLocalPaths Include="$(WpfRepoRoot)\artifacts\packaging\$(WpfConfig)\$(WpfOuputFolder)\runtimes\$(RuntimeIdentifier)\native\*.dll" /><ReferenceCopyLocalPaths Include="$(WpfRepoRoot)\artifacts\packaging\$(WpfConfig)\$(WpfOuputFolder)\runtimes\$(RuntimeIdentifier)\native\*.pdb" /></ItemGroup>
</Project>

以上代码放在github 和 gitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 7fb51bcfda7ac0533999c1bff2dbda9054fc2cab

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 7fb51bcfda7ac0533999c1bff2dbda9054fc2cab

获取代码之后,进入 FerlallcabemberhalairBakifene 文件夹。记得替换 csproj 文件的 C:\lindexi\Code\WPF 为你的 WPF 文件夹

当前的 WPF 在 https://github.com/dotnet/wpf 完全开源,使用友好的 MIT 协议,意味着允许任何人任何组织和企业任意处置,包括使用,复制,修改,合并,发表,分发,再授权,或者销售。在仓库里面包含了完全的构建逻辑,只需要本地的网络足够好(因为需要下载一堆构建工具),即可进行本地构建

更多 WPF 已知问题请参阅我的 博客导航

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/796221.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPF 尝试使用 WinML 做一个简单的手写数字识别应用

最近我看了微软的 AI 训练营之后,似乎有点了解 Windows Machine Learning 和 DirectML 的概念,于是我尝试实践一下,用 WPF 写一个简单的触摸手写输入的画板,再使用大佬训练好的 mnist.onnx 模型,对接 WinML 实现一个简单的手写数字识别应用最近我看了微软的 AI 训练营之后…

VisualStudio 2022 找不到内存 反汇编 寄存器调试工具

本文将告诉大家如何解决在 VisualStudio 2022 的 调试-窗口 里面找不到内存、 反汇编、 寄存器这三个调试工具的问题找不到的原因是没有启用地址级调试 只需要在“工具”(或“调试”)>“选项”>“调试”中选择“启用地址级调试” 然后进行调试即可看到开启之后,即可在…

【TS】TypeScript基础详解【一】

Javascript 类型缺陷 类型引发的问题 在编程开发中,有一个共识:错误越早发现,就越容易解决。 例如:能在代码编写时发现错误,就不要等到代码编译时才发现(这也是IDE的优势之一)。 能在代码编译时发现错误,就不要在代码运行时才发现(类型检测可以帮助我们在这 方面做得很好…

【OpenFeign 】OpenFeign 的常用配置

1 前言 上节我们看了下 OpenFeign 里的重试,在从源码的角度看它的执行原理的时候,又意外的遇到了一个【OpenFeign 】OpenFeign 下未开启重试,服务却被调用了两次 的问题的分析,后面我们又看了重试器的入场和执行时机,那么本节我们看看 OpenFeign 的一些常用配置,以及全局…

Splay 浅谈

Splay 树 定义 Splay 树是一个二叉平衡搜索树,它可以通过 Splay 操作 将一个结点旋转至根结点或者一个给定的结点的下一层,使得整棵树仍然满足二叉搜索树的性质。 Splay 树可以在均摊 \(O(\log n)\) 的时间内完成查找、插入、查询、删除等操作。二叉搜索树的定义:空树是一个…

从kmp到AC自动机

知道kmp的请跳过这一段 找到最清晰的解析 kmp 我看了约114514个解析才搞懂 如何求next 首先,next[i]本应表示0~i的字符串的最长相同前缀后缀的长度。 不过为了方便匹配,实际可以存最长相同前缀后缀时前缀最后一个的地址 听起来好绕 那这么说吧: 例如串 abaabaabaab next[0]=…

LinkedHashMap原理详解—从LRU缓存机制说起

写在前面 从一道Leetcode题目说起 首先,来看一下Leetcode里面的一道经典题目:146.LRU缓存机制,题目描述如下:请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类:LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 in…

微积分快速入门1部分:直觉

1 一分钟微积分:X射线和延时视觉 我们通常只看到图形、公式和情况的表面价值。微积分为我们提供了两种深入挖掘的超能力:X射线能看到图案中隐藏的部分。你不仅能看到树,还能知道它是由年轮组成的,在我们说话的同时,另一个年轮也在生长。延时视觉你能看到物体未来的运行轨迹…

torch.nn.Embedding的导入与导出

简介及导入转自:torch.nn.Embedding使用在RNN模型的训练过程中,需要用到词嵌入,使用torch.nn.Embedding可以快速的完成:只需要初始化torch.nn.Embedding(n,m)即可(n是单词总数,m是词向量的维度)(n是嵌入字典的大小,m是嵌入向量的维度。)。 注意: embedding开始是随机的…

第十七讲 为什么这些SQL语句逻辑相同,性能却差异巨大?

第十七讲: 为什么这些SQL语句逻辑相同,性能却差异巨大? 简概:引入: ​ 在 MySQL 中,有很多看上去逻辑相同,但性能却差异巨大的 SQL 语句。对这些语句使用不当的话,就会不经意间导致整个数据库的压力变大。我今天挑选了三个这样的案例和你分享。希望再遇到相似的问题时,…

2024-09-12 TypeError: Cannot read properties of undefined (reading 0) ==》检查未定义的对象or数组

TypeError: Cannot read properties of undefined (reading 0) ==》TypeError:无法读取undefined的属性(读取“0”) 请记住出现这种错误大多数都是因为你读取了未定义的对象或数组 排查结果:后端返回的id由原来的小写id改成了大写Id。 666,服了,哥们。

UNO 已知问题 在后台线程触发 SKXamlCanvas 的 Invalidate 且在 PaintSurface 事件抛出异常将炸掉应用

本文记录一个 UNO 已知问题,在 UNO 里面可以利用 SKXamlCanvas 对接 Skia 绘制到应用里面。如果此时在后台线程里面调用 SKXamlCanvas 的 Invalidate 触发界面的重新刷新,但在具体的执行绘制 PaintSurface 事件里面对外抛出异常,将会导致应用炸掉背景: 我准备在 UNO 里面将…