WPF的画图请求是如何生成和派发
WPF通过System.Windows.Media.Composition.DUCE相关函数跟worker thread通信,具体来说是通过kernel object来实现的。仔细研究了DUCE内相关函数后,发现这个东西相当有货,从暴露出来的函数名字,就可以猜测到UI thread和render thread的交互模型。应该是UI thread准备好足够的数据后,然后发送请求通知Render thread,然后Render thread拿到对应的请求完成工作。从名字上看,下面这个函数就非常的值得关注:
System.Windows.Media.Composition.DUCE+Channel.SendCommand
接下来的任务就是通过调试器来进一步分析UI thread中具体发生了些什么事情。如果能拿到足够多的callstack sample,这对于通过reflector研究WPF实现是很有帮助的。我的下一个目标是,分析WPF的窗口绘画到底是如何进行的。
观察WPF窗口绘画的简单办法就是写一个死循环,在循环中做一些重画的工作。我这里选择改变窗口背景。我决定用下面的办法来做:
void button2_Click(Object sender, RoutedEventArgs e)
{
while(true)
{
this.Background=new SolidBrush(...); //red
this.Background=new SolidBrush(...); //black
}
}
很可惜,我发现这个方法不能循环改变窗口背景。我决定换一个方法,在timer里面来做这个事情。在WPF的帮助文档中找到DispatchTimer,成功实现了窗口背景的循环重绘。在窗口死循环绘制的时候,用Windbg断下来,检查System.Windows.Media.Composition.DUCE+Channel.SendCommand函数的触发情况:
0:000> !name2ee PresentationCore.dll!System.Windows.Media.Composition.DUCE+Channel.SendCommand
Module: 53cb8000 (PresentationCore.dll)
Token: 0x060004e2
MethodDesc: 53e2d808
Name: System.Windows.Media.Composition.DUCE+Channel.SendCommand(Byte*, Int32)
JITTED Code Address: 538f61c0
0:000> bp 538f61c0
0:000> g
Breakpoint 0 hit
eax=013c4428 ebx=00000007 ecx=013c4428 edx=0012e888 esi=013c4428 edi=013e6e14
eip=538f61c0 esp=0012e880 ebp=013ff898 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
PresentationCore_ni+0xb61c0:
538f61c0 55 push ebp
0:000> !clrstack
OS Thread Id: 0xffc (0)
ESP EIP
0012e880 538f61c0 System.Windows.Media.Composition.DUCE+Channel.SendCommand(Byte*, Int32)
0012e888 539169f2 System.Windows.Media.Composition.DUCE+CompositionNode.SetContent(ResourceHandle, ResourceHandle, Channel)
0012e89c 538e4f2e System.Windows.UIElement.RenderContent(System.Windows.Media.RenderContext, Boolean)
0012e8b4 538ea5ed System.Windows.Media.Visual.RenderRecursive(System.Windows.Media.RenderContext)
0012e8f8 538eabcf System.Windows.Media.Visual.UpdateChildren(System.Windows.Media.RenderContext, ResourceHandle)
0012e920 538ea652 System.Windows.Media.Visual.RenderRecursive(System.Windows.Media.RenderContext)
0012e964 538ea3ea System.Windows.Media.Visual.Render(System.Windows.Media.RenderContext, UInt32)
0012e980 53914b9a System.Windows.Media.CompositionTarget.Compile(Channel)
0012e994 539121d5 System.Windows.Media.CompositionTarget.System.Windows.Media.ICompositionTarget.Render(Boolean, Channel)
0012e9bc 539119ee System.Windows.Media.MediaContext.Render(System.Windows.Media.ICompositionTarget)
0012ea80 53910f87 System.Windows.Media.MediaContext.RenderMessageHandlerCore(System.Object)
0012eac8 53910ea0 System.Windows.Media.MediaContext.RenderMessageHandler(System.Object)
0012eae0 5686ec0a System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Boolean)
0012eafc 5686eb3f System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Boolean, System.Delegate)
0012eb44 56874138 System.Windows.Threading.DispatcherOperation.InvokeImpl()
0012eb80 56874020 System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(System.Object)
0012eb88 79360acf System.Threading.ExecutionContext.runTryCode(System.Object)
0012efb0 79e7be1b [HelperMethodFrame_PROTECTOBJ: 0012efb0] System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object)
0012f018 79360a1b System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0012f030 7936090e System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0012f048 56873fa4 System.Windows.Threading.DispatcherOperation.Invoke()
0012f05c 56873f02 System.Windows.Threading.Dispatcher.ProcessQueue()
0012f098 56873ce6 System.Windows.Threading.Dispatcher.WndProcHook(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
0012f0f0 5686eea3 MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
0012f12c 5686edbc MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)
0012f150 5686ec0a System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Boolean)
0012f16c 5686eb3f System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Boolean, System.Delegate)
0012f1b4 5686e53f System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Boolean)
0012f204 5686e3d5 System.Windows.Threading.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority, System.Delegate, System.Object)
0012f228 5686dcba MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)
0012f3a8 003921bc [NDirectMethodFrameStandalone: 0012f3a8] MS.Win32.UnsafeNativeMethods.DispatchMessage(System.Windows.Interop.MSG ByRef)
0012f3b8 56873370 System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
0012f408 56873213 System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
0012f424 5687316d System.Windows.Threading.Dispatcher.Run()
0012f430 54f1db7f System.Windows.Application.RunInternal(System.Windows.Window)
0012f460 54f1d9a9 System.Windows.Application.Run(System.Windows.Window)
0012f470 54f1d5d6 System.Windows.Application.Run()
0012f47c 00df00ad WPFThreadingModel.App.Main()
0012f69c 79e7be1b [GCFrame: 0012f69c]
很好,很有货!这里不单单看到DUCE的函数触发,更重要的是,这里看到了Visual/MediaContext Class的Render方法。光从名字上看,就知道顺藤摸瓜,就能看到不少好东西。
高兴之余发现了另外一个问题。在上面的callstack中,我怎么没有看到自己改变背景的方法?如果没有的话,这个Render是怎么被触发的呢?另外在callstack上看到,这个Render方法其实是Dispatcher.ProcessQueue方法调用的。ProcessQueue方法是Dispatcher.WndProcHook方法调用的。为什么这个方法叫做WndProcHook呢?跟Windows Message相关吗?拿出Reflector看看:
else if (msg == _msgProcessQueue)
{
this.ProcessQueue();
}
原来的确跟Windows Message相关。当msg的值是_msgProcessQueue的时候就处理Queue。_msgProcessQueue是什么消息呢?检查Dispatcher的构造函数,发现:
_msgProcessQueue = UnsafeNativeMethods.RegisterWindowMessage("DispatcherProcessQueue");
原来是自定义的消息呀!也就是说,重绘的消息是通过自定义的Windows Message通知的。那这个Message是谁发的呢?如果你稍微熟悉Windows message,你就会知道这里绝对会使用PostMessage来发送这个通知,而不是SendMessage。所以我接下来就是要在PostMessage上面设定断点进行观察:
0:000> bc *
0:000> bp User32!PostMessageW
多g几次后,你就会看到:
0:000> !clrstack
OS Thread Id: 0xffc (0)
ESP EIP
0012e5c8 77d18ccb [NDirectMethodFrameStandalone: 0012e5c8] MS.Win32.UnsafeNativeMethods.TryPostMessage(System.Runtime.InteropServices.HandleRef, Int32, IntPtr, IntPtr)
0012e5e4 56872ef8 System.Windows.Threading.Dispatcher.RequestForegroundProcessing()
0012e604 56872e09 System.Windows.Threading.Dispatcher.RequestProcessing()
0012e614 56872c2c System.Windows.Threading.Dispatcher.BeginInvokeImpl(System.Windows.Threading.DispatcherPriority, System.Delegate, System.Object, Boolean)
0012e660 56872b6e System.Windows.Threading.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority, System.Delegate, System.Object)
0012e670 5390194e System.Windows.Media.MediaContext.PostRender()
0012e67c 53910bbf System.Windows.Media.MediaContext.BeginInvokeOnRender(System.Windows.Threading.DispatcherOperationCallback, System.Object)
0012e694 53910b32 System.Windows.ContextLayoutManager.NeedsRecalc()
0012e6a0 53910948 System.Windows.ContextLayoutManager+LayoutQueue.Add(System.Windows.UIElement)
0012e6b4 538e26b7 System.Windows.UIElement.InvalidateArrange()
0012e6c4 538e26da System.Windows.UIElement.InvalidateVisual()
0012e6cc 54f2926d System.Windows.FrameworkElement.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs)
0012e740 5687729b System.Windows.DependencyObject.NotifyPropertyChange(System.Windows.DependencyPropertyChangedEventArgs)
0012e774 568771eb System.Windows.DependencyObject.UpdateEffectiveValue(System.Windows.EntryIndex, System.Windows.DependencyProperty, System.Windows.PropertyMetadata, System.Windows.EffectiveValueEntry, System.Windows.EffectiveValueEntry ByRef, Boolean, System.Windows.OperationType)
0012e80c 56876ccc System.Windows.DependencyObject.InvalidateProperty(System.Windows.DependencyProperty)
0012e83c 54f66445 System.Windows.StyleHelper.InvalidateDependents(System.Windows.Style, System.Windows.FrameworkTemplate, System.Windows.DependencyObject, System.Windows.DependencyProperty, MS.Utility.FrugalStructList`1<System.Windows.ChildPropertyDependent> ByRef, Boolean)
0012e880 54f60df2 System.Windows.StyleHelper.OnTriggerSourcePropertyInvalidated(System.Windows.Style, System.Windows.FrameworkTemplate, System.Windows.DependencyObject, System.Windows.DependencyProperty, System.Windows.DependencyPropertyChangedEventArgs, Boolean, MS.Utility.FrugalStructList`1<MS.Utility.ItemStructMap`1<System.Windows.TriggerSourceRecord>> ByRef, MS.Utility.FrugalMap ByRef, Int32)
0012e8dc 54f28f0a System.Windows.FrameworkElement.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs)
0012e950 5687729b System.Windows.DependencyObject.NotifyPropertyChange(System.Windows.DependencyPropertyChangedEventArgs)
0012e984 568771eb System.Windows.DependencyObject.UpdateEffectiveValue(System.Windows.EntryIndex, System.Windows.DependencyProperty, System.Windows.PropertyMetadata, System.Windows.EffectiveValueEntry, System.Windows.EffectiveValueEntry ByRef, Boolean, System.Windows.OperationType)
0012ea1c 5687673f System.Windows.DependencyObject.SetValueCommon(System.Windows.DependencyProperty, System.Object, System.Windows.PropertyMetadata, Boolean, System.Windows.OperationType, Boolean)
0012ea90 56876293 System.Windows.DependencyObject.SetValue(System.Windows.DependencyProperty, System.Object)
0012eaa4 55022c88 System.Windows.Controls.Control.set_Background(System.Windows.Media.Brush)
0012eab0 00df0571 WPFThreadingModel.Window1.dt_Tick(System.Object, System.EventArgs)
0012ead4 56884b92 System.Windows.Threading.DispatcherTimer.FireTick(System.Object)
0012eae0 5686ec0a System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Boolean)
0012eafc 5686eb3f System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Boolean, System.Delegate)
0012eb44 56874138 System.Windows.Threading.DispatcherOperation.InvokeImpl()
0012eb80 56874020 System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(System.Object)
0012eb88 79360acf System.Threading.ExecutionContext.runTryCode(System.Object)
0012efb0 79e7be1b [HelperMethodFrame_PROTECTOBJ: 0012efb0] System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object)
0012f018 79360a1b System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0012f030 7936090e System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0012f048 56873fa4 System.Windows.Threading.DispatcherOperation.Invoke()
0012f05c 56873f02 System.Windows.