WPF 渲染原理

对于开发者来说,WPF 中最主要的知识点就是渲染,因为 WPF 是一个界面框架。想用一篇博客就能告诉大家完整的 WPF 渲染原理是不可能的。本文向大家介绍从开发者执行绘图指令到在屏幕显示的过程。本文是从一个全局的角度来看渲染的过程,在本文之后会补充很多博客来告诉大家渲染的细节。

从 WPF 画图像到屏幕显示是比较复杂的,本渣也不敢说这就是 WPF 的做法,但是看了很多博客,好像都是这么说的,看了代码,好像 WPF 是这样写的。

本文将会分为三个不同的层来讲,第一层就是 WPF 的总体结构,第二层是消息循环相关,第三层是讲 dx 渲染到屏幕。

WPF 组成


因为 WPF 是一个 UI 框架,作为一个框架最主要的是交互和显示。本文只告诉大家渲染的原理。但是本文不会告诉大家任何关于渲染的算法,只是告诉大家渲染的过程如何从 WPF 元素显示到屏幕。

下面的图片来源 《WPF Architecture》[1]

640?wx_fmt=jpeg

WPF 有三个主要的模块 PresentationFramework、PresentationCore 和基础层。

在 WPF 最顶层,也就是给开发者使用的元素,元素的显示就是使用 DrawingContext 告诉 WPF 需要如何渲染。最简单的方法也就是继承 FrameworkElement 然后重写 OnRender 方法,通过 OnRender 方法画出最基础的界面。这就是在框架的顶层,在这的上面就不属于底层框架的,如在显示上面封装的 Image 等。

那么在调用 OnRender 画出内容之后,是不是 WPF 会立刻显示?如果大家有看 《WPF 使用 Direct2D1 画图入门[2]就会发现,在渲染时调用 CompositionTarget.Rendering 才知道是什么时候渲染,因为 WPF 是分开渲染和交互的,实际的 OnRender 画出的内容的代码是指导渲染,也就是告诉 WPF 如何渲染。那么 WPF 在什么时候渲染就是代码不知道的。为什么 WPF 需要这样做还需要从 WPF 的体系结构开始说起。

我在下面偷了一张图,图片源自《Overview of Windows Presentation Foundation (WPF) Architecture[3],在 WPF 可以分为三层。第一层就是 WPF 的托管层,这一层的代码都是托管代码。第二层就是 WPF 的非托管层,包括刚才告诉大家的模块。最后一层就是系统核心元素层。下面简单介绍一下 WPF 的体系结构。

640?wx_fmt=jpeg

如果觉得对 WPF 的体系结构已经足够了解,那么请跳到下一节。

托管层

在托管层最重要的就是 Presentation Framework、Presentation Core 和 Window Base,很多小伙伴说的 WPF 只是包含这几个模块,因为其他的模块几乎都不会知道。也就是基本使用的类都在下面三个 dll 可以找到。

  • PresentationFramework.dll 提供最顶层封装,包括应用的窗口、控制的 Panel 和 Styles 这些类都是在这可以找到,此外还有交互的控制,动画和几乎可以用到的界面的控件;

  • PresentationCore.dll 提供底层的 WPF 功能,如2d、3d和 geometry 这些类,在 PresentationCore 是对 MIL 和托管中间封装,包括提供了 UI Element 和 Visual 这些类,在显示模块包含视觉树和显示指令,也就是刚才说的 OnRender 重写方法;

  • WindowsBase.dll 提供最底层的基础类,包括调度对象 Dispatcher objects 和依赖属性。

非托管层

非托管层用来进行高性能的 DX 渲染和连接非托管层。

  • milCore.dll 用来作为 WPF 的组合引擎,这是一个使用本地代码编译的库,包含最主要的媒体集成层 Media Integration Layer (MIL) 的基础支持,作用是封装 Dx 的接口支持 2D 和 3D 的渲染,使用本地代码编译是为了获得最好的性能,而且用在 WPF 上层和底层的 DirextX 和 User32 的接口之间。值的一说的是在 Windows Vista 的桌面窗口管理器(Desktop Windows Manager,DWM)就是使用 milCore.dll 渲染桌面的;

  • WindowsCodecs.dll 这是另一个底层的图片支持代码,用来支持 WPF 旋转、放大图片等,也是使用本地代码编译的,提供了很多图片的加密解密,可以让 WPF 把图片画在屏幕。

核心系统层

这一层就是系统的核心,如 User32、GDI、Device Drivers、显卡等,这些组合在程序里是最底层的接口。

  • User32 提供内存和进程分割,这是一个通用的 API ,不止是给 WPF 使用,这个库决定一个元素可以在屏幕的哪里显示,也就是窗口显示的最底层的代码就在这。但是这个代码只提供让窗口在哪里显示,如何显示就需要下面的代码;

  • DirectX 这就是 WPF 渲染的最底层的库,可以渲染 WPF 的几乎所有控件,需要注意 WPF 使用的是 Dx9 或 Dx12 fl9 没有充分使用 Dx 进行画出现代的窗口;

  • GDI 这个代码依赖显卡,是进行 CPU 渲染的接口,提供了绘制原语和提高质量;

  • CLR 因为 WPF 现在还运行在 dotnet framework,所以需要使用运行时,其提供了普通的类和方法,用来方便开发者,大概有 30 万 API 可以使用;

  • Device Drivers 设备驱动程序是在系统特殊的情况下用来驱动一些硬件。

在下面使用到的代码实际都是从最上层拿到的,只有最上层的代码,微软才会开放。而关键的 milCore 代码我还拿不到,只能通过 WinDbg 拿到调用的堆栈。现在还没有完全知道 milCore 的过程,所以本文也无法告诉大家。

本文的顺序是从消息调度到开发者使用 OnRender 方法给绘制原语,再到如何把绘制原语给渲染线程的过程。从渲染线程调用 milCore,在通过 milCore 调用 DirectX 的过程就先简单说过。从 DirectX 绘制完成到屏幕显示的过程也是简单告诉大家。

消息循环


在 WPF 中也是使用消息循环,因为在之前的很多程序都是需要自己写消息循环才可以收到用户的交互,这里消息循环就是 Windows 会向 WPF 发送一些消息,而且 WPF 也可以给自己发消息,通过消息就可以判断当前软件需要做哪些。

处理消息的最主要的类是 HwndSubclass,在创建应用时会执行 Attach 函数,这个函数请看代码:

internal IntPtr Attach(IntPtr hwnd)	
{	// 忽略代码	return this.CriticalAttach(hwnd);	
}

从上面代码可以看到主要的是 CriticalAttach 函数,请看代码:

internal IntPtr CriticalAttach(IntPtr hwnd)	
{	// 忽略代码	NativeMethods.WndProc newWndProc = new NativeMethods.WndProc(this.SubclassWndProc); // 创建处理消息	IntPtr windowLongPtr = UnsafeNativeMethods.GetWindowLongPtr(new HandleRef((object) this, hwnd), -4);	this.HookWindowProc(hwnd, newWndProc, windowLongPtr);	return (IntPtr) this._gcHandle;	
}

这里 SubclassWndProc 就是主要处理消息,但是在上面代码是 HookWindowProc 注册。在 GetWindowLongPtr 大家也许都很熟悉,拿到这个函数就是拿到窗口,下面来看一下 HookWindowProc 代码:

private void HookWindowProc(IntPtr hwnd, NativeMethods.WndProc newWndProc, IntPtr oldWndProc)	
{	_hwndAttached = hwnd;	_hwndHandleRef = new HandleRef(null,_hwndAttached);	_bond = Bond.Attached;	_attachedWndProc = newWndProc;	_oldWndProc = oldWndProc;	// 这有下面的代码才是获得消息	IntPtr oldWndProc2 = (IntPtr)UnsafeNativeMethods.CriticalSetWindowLong(_hwndHandleRef, NativeMethods.GWL_WNDPROC, _attachedWndProc);	// 跟踪这个窗口,可以在 CLR 关闭,撤掉管理的窗口代码	ManagedWndProcTracker.TrackHwndSubclass(this, _hwndAttached);	
}

里 NativeMethods.GWL_WNDPROC 的值是 -4,在 CriticalSetWindowLong 是这样写:

internal static IntPtr CriticalSetWindowLong(HandleRef hWnd, int nIndex, NativeMethods.WndProc dwNewLong)	
{	int errorCode;	IntPtr retVal;	retVal = NativeMethodsSetLastError.SetWindowLongPtrWndProc(hWnd, nIndex, dwNewLong);	errorCode = Marshal.GetLastWin32Error();	return retVal;	
}

640?wx_fmt=jpeg

所以核心就是 SetWindowLongPtrWndProc 调用 SetWindowLongWrapper 设置获得消息。

那么消息是如何作为事件的?

internal IntPtr SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)	
{	// 忽略的代码	// 获得当前线程的 dispatcher 也就是不是主线程也可以处理	Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread);	// 忽略的代码	DispatcherOperationCallbackParameter param = _paramDispatcherCallbackOperation;	_paramDispatcherCallbackOperation = null;	param.hwnd = hwnd;	param.msg = msg;	param.wParam = wParam;	param.lParam = lParam;	// 插入处理的消息	object result = dispatcher.Invoke(	DispatcherPriority.Send, 	_dispatcherOperationCallback,	param);	var retval = param.retVal;	return retval;	
}

也就是调用的 SubclassWndProc 实际上就是封装消息,然后调用 dispatcher 执行。那么 _dispatcherOperationCallback 有是如何做的?实际上这个也是调用这个弱引用委托,请看代码,下面的代码是去掉判断参数:

private object DispatcherCallbackOperation(object o)	
{	DispatcherOperationCallbackParameter param = (DispatcherOperationCallbackParameter)o;	param.handled = false;	param.retVal = IntPtr.Zero; // 把返回值设置为 空,把是否处理设置为 false ,在下面调用函数,如果可以处理就设置 param.handled 为 true	HwndWrapperHook hook = _hook.Target as HwndWrapperHook;	param.retVal = hook(param.hwnd, param.msg, param.wParam, param.lParam, ref param.handled);	return param;	
}

那么这个 Handle 有什么用?这个属性就是在为 false 时会在 SubclassWndProc 使用下面代码:

// 如果窗口无法处理这个消息,把他发到下一个链,因为 Windows 消息是可以传到下一个窗口	
if(!handled)	
{	retval = CallOldWindowProc(oldWndProc, hwnd, message, wParam, lParam);	
}

那么这个处理 _hook 是怎么传过来的,这个_hook 是一个弱引用,具体的传送过来需要从 Dispatcher 开始说,请看下面。

Dispatcher 调度


关于调度需要从 PushFrame 开始说,请看《深入了解 WPF Dispatcher 的工作原理(PushFrame 部分) - walterlv[4],walterlv大佬已经告诉大家底层的原理,我就可以不再这里详细告诉大家。

在 Dispatcher 的构造可以看到下面代码:

MessageOnlyHwndWrapper window = new MessageOnlyHwndWrapper();

创建 MessageOnlyHwndWrapper 可以收到消息,并且把消息放在 Dispatcher 用来处理,那么 MessageOnlyHwndWrapper 为什么可以收到消息? 可以看到 MessageOnlyHwndWrapper 的代码很简单:

internal class MessageOnlyHwndWrapper : HwndWrapper	
{	public MessageOnlyHwndWrapper() : base(0, 0, 0, 0, 0, 0, 0, "", NativeMethods.HWND_MESSAGE, null)	{	}	
}

就需要看一下 HwndWrapper 类的代码,这个类的构造函数的关键代码:

public HwndWrapper()//太多参数,这里就不写了	
{	_wndProc = new SecurityCriticalData<HwndWrapperHook>(new HwndWrapperHook(WndProc));	// 还记得刚才说的消息,就是在这里创建 HwndSubclass 可以直接拿到消息	HwndSubclass hwndSubclass = new HwndSubclass(_wndProc.Value);	
}

所以在上面说的 _hook 弱引用就是 传入的_wndProc ,那么这个 _wndProc 的关键代码就是WndProc,在 HwndWrapper 的函数:       

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)	
{	IntPtr result = IntPtr.Zero;	WindowMessage message = (WindowMessage)msg;	// 遍历所有的 _hooks 处理消息	foreach(HwndWrapperHook hook in _hooks.Value)	{	result = hook(hwnd, msg, wParam, lParam, ref handled);	if(handled)	{	break;	}	}	return result;	
}

所以这个方法也就是没有做具体处理,是通过AddHook函数添加的处理:

public void AddHook(HwndWrapperHook hook)	
{	//VerifyAccess();	if(_hooks == null)	{	_hooks = new SecurityCriticalDataClass<WeakReferenceList>(new WeakReferenceList());	}	_hooks.Value.Insert(0, hook);	
}

因为垃圾微软的代码,所以才在 Dispatcher 的构造函数调用 AddHook ,也就是在构造函数创建了 MessageOnlyHwndWrapper 在这个类初始化,具体处理是在初始化之后才加上,所以可以看到这个类有很多没有用的代码,因为初始化创建的值都是空,在创建之后才加上:

private Dispatcher()	
{	MessageOnlyHwndWrapper window = new MessageOnlyHwndWrapper();	_hook = new HwndWrapperHook(WndProcHook);	window.AddHook(_hook);	
}

640?wx_fmt=jpeg

最核心的处理消息就是 Dispatcher 的 WndProcHook,这个方法实际上只是调用 ProcessQueue 方法。

640?wx_fmt=jpeg

在 SubclassWndProc 调用的dispatcher.Invoke( DispatcherPriority.Send, _dispatcherOperationCallback, param)就是调用WndProcHook 函数传入参数。

如果收到了画窗口的消息,就会把这个消息发送给DWM,通过DWM把窗口画的内容画到屏幕。

RenderContent


在使用绘制原语之后,最底层的函数是 UIElement.RenderContent ,请看代码:

internal override void RenderContent(RenderContext ctx, bool isOnChannel)	
{	DUCE.Channel channel = ctx.Channel;	DUCE.IResource drawingContent = (DUCE.IResource) this._drawingContent;	drawingContent.AddRefOnChannel(channel);	DUCE.CompositionNode.SetContent(this._proxy.GetHandle(channel), drawingContent.GetHandle(channel), channel);	this.SetFlags(channel, true, VisualProxyFlags.IsContentConnected);	
}

640?wx_fmt=jpeg

通过 DUCE.CompositionNode.SetContent 调用 channel.SendCommand 就可以发送到渲染线程:  

internal static unsafe void SetContent(DUCE.ResourceHandle hCompositionNode, DUCE.ResourceHandle hContent, DUCE.Channel channel)	
{	DUCE.MILCMD_VISUAL_SETCONTENT visualSetcontent;	visualSetcontent.Type = MILCMD.MilCmdVisualSetContent;	visualSetcontent.Handle = hCompositionNode;	visualSetcontent.hContent = hContent;	// 通过下面代码发送到另一个线程 	channel.SendCommand((byte*) &visualSetcontent, sizeof (DUCE.MILCMD_VISUAL_SETCONTENT));	
}

640?wx_fmt=jpeg

那么这个渲染是如何触发的?实际上是在 Dispatcher.ProcessQueue 调用,在上面已经有告诉大家 ProcessQueue 是在 WndProcHook 触发的:

private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)	
{	if(message == _msgProcessQueue)	{	ProcessQueue();	}	
}

这里 _msgProcessQueue 就是在 Dispatcher 静态构造函数的自定义消息:

static Dispatcher()	
{	_msgProcessQueue = UnsafeNativeMethods.RegisterWindowMessage("DispatcherProcessQueue");	// 下面还有很多代码	
}

640?wx_fmt=jpeg

那么这个消息是怎么发送的?在UIElement.InvalidateVisual函数会调用MediaContext.PostRender这里就发送自定义消息,于是就可以开始渲染。

在消息循环是会不断获取消息,这里说的渲染是包括两个方面,一个是 WPF 把内容画到窗口,也就是上面说的自定义消息,还有另一个就是把窗口内容画在屏幕。这两个都是依靠 Windows 消息,只是第一个消息是 WPF 自己发给自己,也就是自己玩的。从 Dispatcher 拿到自定义的消息,就开始执行视觉树的对象,调用对应的绘制,这里是收集到绘制原语,也就是告诉显卡可以怎么画。在底层是通过 System.Windows.Media.Composition.DUCE 的 Channel 把数据发送到渲染线程,渲染线程就是使用 Dx 进行绘制。在 Dx 画是使用 milCore 从渲染线程连接到 Dx 画出来的。

在渲染线程收集到的都是绘制原语,绘制原语就是在 Visual 底层调用的DrawingContext 传入的方法。

640?wx_fmt=jpeg

这一部分没有完全跟源代码,如果有哪些地方和实际不相同,请告诉我。

渲染线程拿到了绘制原语就可以进行绘制,绘制的过程需要进行处理图片和一些基础形状,大概的过程请看下面:

640?wx_fmt=jpeg

这时到了 Dx 才会使用显卡进行渲染,并且绘制的是窗口指针。也就是窗口绘制完成在屏幕还是无法看到的。

在绘制的时候需要使用 MIL 解码一些图片和一些形状才可以用到 dx 进行渲染。

640?wx_fmt=jpeg

在窗口画完之后,会通过 WM_PAINT 告诉 DWM 可以画出窗口。但是现代的应用是不需要在窗口刷新的过程通过 windows 消息发送到 DWM 才进行窗口刷新。只有在窗口存在部分不可见,如窗口有一部分在屏幕之外,从屏幕外移到屏幕内,或者窗口最小化到显示才需要通过 windows 告诉 DWM 刷新。

640?wx_fmt=jpeg

那么这里的 DWM 是什么?请看下面。

桌面窗口管理


在 Windows 系统,很重要的就是 DWM(Desktop Window Manager),它可以把窗口画到屏幕,并且支持窗口做半透明和其他的对其他窗口的视觉处理。

需要知道,发送 WM_PAINT 消息只能通过系统发送而不能通过应用发送,也就是上面说的通过 WM_PAINT 告诉 DWM 可以画出窗口,不是软件主动告诉系统,而是系统会不断刷新。

对于不可见的窗口,在 DWM 是不会发送 WM_PAINT 到这个窗口。

详细的 WM_PAINT 请看《 WMPAINT详解和WMERASEBKGND - CSDN博客[5]

如果系统没有发送 WM_PAINT 到应用,屏幕怎么知道窗口需要刷新?实际通过《Drawing Without the WM_PAINT Message[6]的方法就可以做到。

在 Windows 8 之后就无法手动设置关闭 DWM 的合成,只有在 windows 7 和以前的系统才可以设置关闭合成。通过 DWM 合成技术可以将每个绘制的窗口认为是一个位图,通过对位图处理添加阴影等,做出好看界面。

更多请看 《Desktop Window Manager[7]

从控件画出来到屏幕显示


虽然上面写了很多,但是好多小伙伴都不会仔细去看,所以本渣就在这里重新把过程说一遍。需要知道,这里说的省略了很多细节,上面的也是有很多细节没有告诉大家。

在渲染的时候,是需要通过多个方式把渲染的任务放在 Dispather 里,在 WPF 应用内是可以通过 InvalidateVisual 的方法通知,而系统也在不断发送消息告诉一个应用开始渲染。

640?wx_fmt=jpeg


在 Dispatcher 收到消息之后就可以把渲染任务放在队列,按照优先级一个个出队。

640?wx_fmt=jpeg

这时在 Dispatcher 内部通过渲染的调用就会通过 DispatcherOperation 执行对应的任务。

渲染的任务流程如下:

mscorlib.dll! System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) 	
->	
WindowsBase.dll! System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs)	
->	
PresentationFramework.dll! System.Windows.Window.ShowHelper(object booleanBox)

最后也就是显示的最近的代码。

640?wx_fmt=jpeg

总的渲染结构请看下图:

640?wx_fmt=jpeg

渲染需要经过消息循环和 Dispatcher 循环,也就是渲染的方法不是直接通过控件调用渲染。控件是通过发送消息经过消息循环再调用到控件的 OnRender 方法。再 OnRender 方法里,经过 Drawing 方法输出绘制原语到渲染线程。渲染线程经过 MIL 和 Dx 渲染界面到窗口。屏幕管理更新窗口让用户在屏幕可以看到。

关于渲染性能请看《WPF Drawing Performance[8]

参考资料


[1] 《WPF Architecture 》https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/wpf-architecture

[2] 《WPF 使用 Direct2D1 画图入门》https://blog.csdn.net/lindexi_gd/article/details/80387784

[3] 《Overview of Windows Presentation Foundation (WPF) Architecture》https://www.c-sharpcorner.com/UploadFile/819f33/overview-of-windows-presentation-foundation-wpf-architectu/

[4] 《深入了解 WPF Dispatcher 的工作原理(PushFrame 部分) - walterlv》https://walterlv.github.io/post/dotnet/2017/09/26/dispatcher-push-frame.html

[5] 《WM_PAINT详解和WM_ERASEBKGND - CSDN博客》https://blog.csdn.net/rankun1/article/details/50596634

[6] 《Drawing Without the WM_PAINT Message》https://docs.microsoft.com/en-us/windows/desktop/gdi/drawing-without-the-wm-paint-message

[7] 《Desktop Window Manager》https://docs.microsoft.com/en-us/windows/desktop/dwm/dwm-overview

[8] 《WPF Drawing Performance》http://kynosarges.org/WpfPerformance.html

[9] 《一站式WPF–Window(一) - 周永恒 - 博客园》https://www.cnblogs.com/Zhouyongh/archive/2009/11/30/1613628.html

[10] 《WPF起步(上) — WPF是如何把图像画到屏幕上 - CSDN博客》https://blog.csdn.net/eparg/article/details/1930357

[11] 《Sharing Message Loops Between Win32 and WPF》https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/sharing-message-loops-between-win32-and-wpf

[12] 《1.3 WPF体系结构 - 51CTO.COM》http://book.51cto.com/art/200908/145309.htm

[13] 《WM_PAINT message》https://docs.microsoft.com/zh-cn/windows/desktop/gdi/wm-paint

[14] 《Custom Window Frame Using DWM》https://docs.microsoft.com/en-us/windows/desktop/dwm/customframe

[15] 《Performance Considerations and Best Practices》https://docs.microsoft.com/en-us/windows/desktop/dwm/bestpractices-ovw

[16] 《Win32知识之窗口绘制窗口第一讲》https://www.cnblogs.com/iBinary/p/9576439.html

[17] 《win32程序之窗口程序以及消息机制》https://www.cnblogs.com/iBinary/p/9580268.html

[18]《课件 WPF 渲染原理》https://r302.cc/p2m3ea

[19]《WPF 渲染相关》https://lindexi.gitee.io/post/%E6%B8%B2%E6%9F%93.html


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

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

相关文章

HDU - 6989 Didn‘t I Say to Make My Abilities Average in the Next Life?! 莫队/单调栈 + 线段树/ST表在线

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 考虑将贡献分开来算&#xff0c;先计算最大值&#xff0c;再算个最小值&#xff0c;之后答案就是((maxmin)/2)/(len∗(len1)/2)((maxmin)/2)/(len*(len1)/2)((maxmin)/2)/(len∗(len1)/2)。…

ASP.NET Core 中的管道机制

首先&#xff0c;很感谢在上篇文章 C# 管道式编程 中给我有小额捐助和点赞的朋友们&#xff0c;感谢你们的支持与肯定。希望我的每一次分享都能让彼此获得一些收获&#xff0c;当然如果我有些地方叙述的不正确或不当&#xff0c;还请不客气的指出。好了&#xff0c;下面进入正文…

微软宣布SQL Server 2019免费支持Java

在 2018 年 9 月的时候&#xff0c;微软就宣布与领先的 Java 开源贡献者和发行商 Azul Systems 建立新的合作伙伴关系。这一关系允许所有 Azure 客户在微软和 Azul 联合提供的支持下&#xff0c;免费使用 Azul 的 Zulu for Azure-Enterprise Java 发行版 。最近&#xff0c;微软…

P4231 三步必杀 二次差分

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 考虑给[2,6][2,6][2,6]加上s2,e10s2,e10s2,e10的等差数列&#xff0c;变成2,4,6,8,102,4,6,8,102,4,6,8,10&#xff0c;考虑差分数组2,2,2,2,2,−102,2,2,2,2,-102,2,2,2,2,−10&#xff0…

创新的迷思——2019 Microsoft Hackathon 苏州站回顾

每年&#xff0c;微软在7月份都会在内部举办一次黑客松&#xff08;Hachathon&#xff09;活动&#xff0c;所有的员工&#xff0c;包括实习生都可以自由组队参赛&#xff0c;在为期一周的时间内完成构想、设计和开发的一系列过程&#xff0c;并且在最后一天进行项目路演和评选…

P4062 [Code+#1]Yazid 的新生舞会 树状数组维护三阶差分

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一个序列aaa&#xff0c;让你求有多少个子区间满足存在一个数是这个区间的绝对众数&#xff0c;绝对众数指该数在区间内出现的次数严格大于r−l12\frac{r-l1}{2}2r−l1​。 n≤5e5,0≤ai≤n−1n\le5e5,0…

一句话概括4本管理著作

之前618活动的时候买了不少书&#xff0c;最近一段时间集中看了四本管理相关的书籍&#xff0c;收获颇丰。在这里分享给大家。如果你不是管理者也没关系&#xff0c;可以换个视角来看看管理者眼中的世界&#xff0c;毕竟&#xff0c;“赤兔马”也需要让“伯乐”看到才行嘛。虽然…

[译].Net中的内存-什么分配在了哪里

原文链接&#xff1a;https://jonskeet.uk/csharp/memory.html人们在理解值类型和引用类型之间的差异时因为“值类型在栈上分配&#xff0c;引用类型在堆上分配”这句话造成了很多混乱。这完全是不对的&#xff0c;本文试图澄清这个问题。变量中有什么&#xff1f;理解.NET中内…

上车时机已到--.NETCore是适应时代发展的雄鹰利剑

要起飞了.NET Core 3.0-prevew7&#xff1a;https://dotnet.microsoft.com/download/dotnet-core/3.0随着 .NET Core 3.0-prevew7 的发布&#xff0c;开源社区的一支重要力量重要即将起飞&#xff1b;官方指出&#xff0c;该预览版为可能为最终版本&#xff0c;在很长一段时间内…

HDU - 7028 Decomposition 无向完全图构造欧拉回路

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一张无向完全图&#xff0c;让你构造kkk个长度分别为lil_ili​的路径&#xff0c;这些路径不相交&#xff0c;且∑lin∗(n−1)2\sum l_i\frac{n*(n-1)}{2}∑li​2n∗(n−1)​。 n≤1000,nmod21n\le1000,…

「Azure」数据分析师有理由爱Azure之二-立即申请帐号开始学习之旅

目前关于Azure的学习资料不多&#xff0c;除了官方的文档和Microsoft Learn频道外&#xff0c;几乎没有什么中文性资料可学习&#xff0c;就算有&#xff0c;也是以IT的思维方式来展开介绍&#xff0c;对没有IT背景的数据分析师来说&#xff0c;非常难于适应。本篇给大家带来一…

矩阵快速幂的最简单用法

矩阵快速幂 链接&#xff1a;https://ac.nowcoder.com/acm/contest/1168/K 来源&#xff1a;牛客网 题目描述 这个勇者明明超强却过分慎重&#xff0c;勇者龙宫院圣哉与n名冒险者一起去讨伐神秘魔物&#xff0c;龙宫院圣哉十分谨慎&#xff0c;他只会在最后一刻出手&#xff…

DevOps案例研究|史上最能“拜客户教”的公司,是如何做到持续交付的?(第1趴)...

内容来源&#xff1a;DevOps案例深度研究 –Amazon持续交付之道战队&#xff08;本文只展示部分PPT及研究成果&#xff0c;更多细节请关注案例分享会&#xff0c;及本公众号。&#xff09;本案例内容贡献者&#xff1a;单冰 (Topic Leader)、 赵栋、梁兴龙、李杰、毛艳清、牛恒…

P2261 [CQOI2007]余数求和 整除分块

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; n,k≤1e9n,k\le1e9n,k≤1e9 思路&#xff1a; 考虑转换式子&#xff0c;∑i1nkmodi∑i1n(k−⌊ki⌋∗i)n∗k−∑i1n⌊ki⌋∗i\sum_{i1}^{n}k\bmod i\sum_{i1}^n(k-\left \lfloor \frac{k}{i} \right \rfloo…

背包问题——第一篇

一&#xff0c;01背包 最简单也是最经典的背包问题。 首先我们知道背包问题是一种d问题&#xff0c;最重要的就是要去找到他的状态转移方程。而在01背包中转移方程就比较简单了&#xff0c;这里用一个二维数组进行标表示。 ans[i][j]max(ans[i-1][j],ans[i-1][j-v[i]w[i]); 在…

小白开学Asp.Net Core 《十》

小白开学Asp.Net Core 《十》 — — Session、Cookie、Cache&#xff08;老生常谈&#xff09;一、背景在常谈Session和Cookie之前我们先来简单的了解下Http&#xff08;可以说这是必须的&#xff0c;默认大家都了解&#xff09;总结一句话&#xff1a;HTTP是一种无状态的协议&…

P3935 Calculating 整除分块

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 我们设s(x)∑i1nf(x)s(x)\sum_{i1}^nf(x)s(x)∑i1n​f(x)&#xff0c;那么答案就是s(r)−s(l−1)s(r)-s(l-1)s(r)−s(l−1)。 容易发现&#xff0c;我们要求的f(x)f(x)f(x)实际上就是xxx的…

Sticks-hdu-1455深度搜索dfs

Sticks 题目传送门。 题目大意是&#xff0c;给你一个数字n代表总共的棍子数量&#xff0c;要做的就是&#xff0c;把这几根棍子拼成长度相同的棍子&#xff0c;并且让所拼成的棍子的长度尽可能地小&#xff0c;也就是拼成的棍子的数量尽可能的多。 在这里很简单的想到要给棍…

gRPC in ASP.NET Core 3.0 -- Protocol Buffer(1)

开发环境&#xff1a;IDE: VSCodeVSCode的扩展插件&#xff1a;vscode-proto3和Clang-Format这两个扩展Windows还需要安装Clang&#xff0c;Windows 64位系统的地址如下&#xff1a;Clang for Windows (64-bit)(http://llvm.org/releases/3.7.0/LLVM-3.7.0-win64.exe)&#xff…

[蓝桥杯2016初赛]方格填数

正如题目&#xff0c;我们要保证相邻的数不相连就行了&#xff0c;一共要填十个数。 下面是我的思路&#xff0c;供大家参考一下&#xff0c; 我把上面的各子放进了这个5*6的矩形&#xff0c;其中标记1–10的分别是要填数的各格子&#xff0c;一开始我先初始化这个二维数组为全…